I'm trying to implement a cropping method myself,using the unsafe code and pointer to speed up the whole process.
This is my code:
private unsafe void Cut(Bitmap bmp, Rectangle r) {
Bitmap result = new Bitmap(r.Width, r.Height, bmp.PixelFormat);
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
BitmapData bmData2 = result.LockBits(new Rectangle(0, 0, result.Width, result.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, result.PixelFormat);
IntPtr scan0 = bmData.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride = bmData.Stride;
int stride2 = bmData2.Stride;
int x = r.X;
int y = r.Y;
int width = r.Width;
int height = r.Height;
for (; y < height; y++) {
byte * p = (byte * ) scan0.ToPointer();
p += y * stride;
byte * p2 = (byte * ) scan02.ToPointer();
p2 += y * stride2;
for (; x < width; x++) {
p2[0] = p[0];
p2[1] = p[1];
p2[2] = p[2];
p2[3]=p[3];
p += 4;
p2 += 4;
}
}
result.Save("a.png");
}
And the call to this method:
Bitmap b = (Bitmap)Bitmap.FromFile(#"C:\Users\itapi\Desktop\1.png");
Rectangle r = new Rectangle(200, 500, 300, 450);
Cut(b, r);
When i run the code,i just get a black rectangle as result...not the pixels i wanted to copy from the intial image.
The image from the example above is in32bpprgb format
I'm not sure what i'm doing wrong..i'll appreciate any help.
Thanks.
On the result, you chose "System.Drawing.Imaging.ImageLockMode.ReadOnly" I think you want ReadWrite or WriteOnly.
When I ran through debugger, this statement: for (; y < height; y++) condition failed immediately on my rather large image. So your loop logic is incorrect for running through the lines... Use a debugger :)
EDIT I ran it through the debugger, and your Y and X logic is wrong. I did a quick fix on the Y logic, and got it too crop. You'll have to do something similar to the X to get the correct crop point.
Try this, it cropped and saved a file:
int x = r.X;
int y = r.Y;
int width = r.Width;
int height = r.Height;
int newY = 0;
for (y = r.Y; y < height+r.Y; y++) //For each line in the old image
{
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
byte* p2 = (byte*)scan02.ToPointer();
p2 += newY * stride2;
for (x=r.X; x < width+r.X; x++)
{
p2[0] = p[0];
p2[1] = p[1];
p2[2] = p[2];
p2[3] = p[3];
p += 4;
p2 += 4;
}
newY++;
}
result.Save("\\a.png");
Related
I am trying to convert some Java code into C# for an application. I can't seem to find the exact equivalent of setPixels method in android.Graphics.Bitmap in C# .NET.
Here is the Java code:
BitMatrix result = some code .....
int w = result.getWidth();
int h = result.getHeight();
int[] pixels = new int[w * h];
for (int y = 0; y < h; y++) {
int offset = y * w;
for (int x = 0; x < w; x++) {
pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, 480, 0, 0, w, h);
Here is my attempt at converting it into C#:
int w = result.Width;
int h = result.Height;
int[] pixels = new int[w * h];
for (int y = 0; y < h; y++)
{
int offset = y * w;
for (int x = 0; x < w; x++)
{
pixels[offset + x] = result[x, y] ? Color.Black.ToArgb() : Color.White.ToArgb();
}
}
Bitmap bitmap = new Bitmap(w, h);
// how to convert the line below
bitmap.setPixels(pixels, 0, 480, 0, 0, w, h);
How should I go about converting the last line into C#. Any suggestions?
The simplest solution is to replace
pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
with
bitmap.SetPixel(x, y, result[x, y] ? Color.Black : Color.White);
This will be rather slow, since it will take some locks for each pixel set. You will also need to create the bitmap first.
A faster way would be to use unsafe code to get a pointer to the bitmap data. See fast work with bitmaps. Assuming an Argb color bitmap
var bData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
byte* scan0 = (int*)bData.Scan0.ToPointer();
for (int y = 0; y < bData.Height; ++y)
{
var rowPtr = scan0 + y * bData.Stride * 4;
for (int x = 0; x < bData.Width; ++x)
{
rowPtr[x] = result[x, y] ? Color.Black.ToArgb() : Color.White.ToArgb();
}
}
I'm trying to set few pixels in a bitmap using unsafe pointer access(for performance boost)-this is my code:
private unsafe void DrawImage(Bitmap bmp1)
{
BitmapData bmData = bmp1.LockBits(new Rectangle(0, 0, bmp1.Width, bmp1.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp1.PixelFormat);
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
int x = 200;
int y = 400;
for (; y < 600; y++) {
byte * p = (byte * ) scan0.ToPointer();
p += y * stride + x * 4;
for (; x < 900; x++) {
p[0] = 0; //blue
p[1] = 0; //green
p[2] = 255; //red
p += 4;
}
}
bmp1.UnlockBits(bmData);
}
As you can see,i'm trying to set a block (to red color) from y=400 to y=600 , and x=200 to x=900.
On every interation of the outer loop i advance the pointer to the desired address ,but i'm getting only a "thin" horizontal line of red pixels...which indicated that somthing is wrong with the y address..i can't get why.
p += y * stride + x * 4;-this is how i advance the pointer everytime... did i miss something?
i hope my question was clear.
Thanks in advance.
The shortcuts in your loops don't go well together. At least the inner loop needs a proper start value. Best to set both start and end values; most likely you will want to go for a rectangle later anyway..
Also, as Hans noted you should make sure to look into the pixel widths of your actual PixelFormat.
Finally the loop advances are not correct.
This should work:
private unsafe void DrawImage(Bitmap bmp1)
{
BitmapData bmData = bmp1.LockBits(new Rectangle(0, 0, bmp1.Width, bmp1.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite,
bmp1.PixelFormat);
// this is only a rather incomplete test, of course:
int pixWidth = bmp1.PixelFormat == PixelFormat.Format24bppRgb ? 3 :
bmp1.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 4;
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
int x0 = 100;
int y0 = 100;
int x1 = 200;
int y1 = 300;
byte* p = (byte*)scan0.ToPointer() + y0 * stride;
for (int y = y0; y < y1; y++)
{
p += stride;
int px = x0 * pixWidth;
for (int x = x0; x < x1; x++)
{
px += pixWidth;
p[px + 0] = 0; //blue
p[px + 1] = 0; //green
p[px + 2] = 255; //red
}
}
bmp1.UnlockBits(bmData);
}
As noted this is not really a performance boost over GDI+ FillRectangle. However if you want to set not a block of pixels and/or not set all to one fixed color only, your code will make sense..
I'm working on a screen sharing app, which runs a loop and grab fast screenshots using GDI methods . example here
Of course I also use a flood fill algorithm to find the changes areas between 2 images (previous screenshot and current).
I use another small trick - I downscale the snapshot resolution in 10, because processing 1920*1080=2073600 pixels very constantly is not very efficient.
However when I find the rectangle bounds - I apply it on the original full size bitmap and I just multiply by 10 the dimension (including top, left, width, height).
This is the scanning code:
unsafe bool ArePixelsEqual(byte* p1, byte* p2, int bytesPerPixel)
{
for (int i = 0; i < bytesPerPixel; ++i)
if (p1[i] != p2[i])
return false;
return true;
}
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
var bmData1 = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
var bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
int bytesPerPixel = 4;
IntPtr scan01 = bmData1.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride1 = bmData1.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
bool[] visited = new bool[nWidth * nHeight];
byte* base1 = (byte*)scan01.ToPointer();
byte* base2 = (byte*)scan02.ToPointer();
for (int y = 0; y < nHeight; y ++)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; ++x)
{
if (!ArePixelsEqual(p1, p2, bytesPerPixel) && !(visited[x + nWidth * y]))
{
// fill the different area
int minX = x;
int maxX = x;
int minY = y;
int maxY = y;
var pt = new Point(x, y);
Stack<Point> toBeProcessed = new Stack<Point>();
visited[x + nWidth * y] = true;
toBeProcessed.Push(pt);
while (toBeProcessed.Count > 0)
{
var process = toBeProcessed.Pop();
var ptr1 = (byte*)scan01.ToPointer() + process.Y * stride1 + process.X * bytesPerPixel;
var ptr2 = (byte*)scan02.ToPointer() + process.Y * stride2 + process.X * bytesPerPixel;
//Check pixel equality
if (ArePixelsEqual(ptr1, ptr2, bytesPerPixel))
continue;
//This pixel is different
//Update the rectangle
if (process.X < minX) minX = process.X;
if (process.X > maxX) maxX = process.X;
if (process.Y < minY) minY = process.Y;
if (process.Y > maxY) maxY = process.Y;
Point n; int idx;
//Put neighbors in stack
if (process.X - 1 >= 0)
{
n = new Point(process.X - 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.X + 1 < nWidth)
{
n = new Point(process.X + 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y - 1 >= 0)
{
n = new Point(process.X, process.Y - 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y + 1 < nHeight)
{
n = new Point(process.X, process.Y + 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
}
//finaly set a rectangle.
Rectangle r = new Rectangle(minX * 10, minY * 10, (maxX - minX + 1) * 10, (maxY - minY + 1) * 10);
rec.Add(r);
//got the rectangle now i'll do whatever i want with that.
//notify i scaled everything by x10 becuse i want to apply the changes on the originl 1920x1080 image.
}
p1 += bytesPerPixel;
p2 += bytesPerPixel;
}
base1 += stride1;
base2 += stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
This is my call:
private void Start()
{
full1 = GetDesktopImage();//the first,intial screen.
while (true)
{
full2 = GetDesktopImage();
a = new Bitmap(full1, 192, 108);//resizing for faster processing the images.
b = new Bitmap(full2, 192, 108); // resizing for faster processing the images.
CodeImage(a, b);
count++; // counter for the performance.
full1 = full2; // assign old to current bitmap.
}
}
However, after all the tricks and techniques I used, the algorithm runs quite slow... on my machine - Intel i5 4670k 3.4ghz - it runs only 20 times (at the maximum! It might get lower)! It maybe sounds fast (don't forget I have to send each changed area over the network after), but I'm looking to achieve more processed image per second. I think the main bottleneck is in the resizing of the 2 images - but I just thought it would be even faster after resizing - because it would have to loop through less pixels... 192*108=200,000 only..
I would appreciate any help, any improvement. Thanks.
I am capturing data from some camera (array of RAW data).
Then I'm mapping this data to RGB values according to color palette.
I need to map it as fast as possible, so I use BitmapDdata and edit pixels in unsafe piece of code using pointers.
public void dataAcquired(int[] data)
{
Bitmap bmp = new Bitmap(width, height);
BitmapData data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
for (int i = 0; i < data.Length; i++)
{
int x = i % bmp.Width;
int y = i / bmp.Width;
Rgb rgb = mapColors[data[i]];
unsafe
{
byte* ptr = (byte*)data.Scan0;
ptr[(x * 3) + y * data.Stride] = rgb.b;
ptr[(x * 3) + y * data.Stride + 1] = rgb.g;
ptr[(x * 3) + y * data.Stride + 2] = rgb.r;
}
}
bmp.UnlockBits(data);
}
And I'm doing this for every incoming frame. It works fine, but it still takes something like 30ms for each frame for 320x240 pixels.
Is it possible to make it even more faster? Maybe I couldlock/unlock data in memory only once, but I'm not sure about this.
Instead of calculating x and y for each pixel, you could make them loop counters, like this:
for( y = 0; y < bmp.Height; y++ )
for( x = 0; x < bmp.Width; x++ )
Better yet, ditch x and y altogether and just keep incrementing the ptr pointer instead of recalculating an offset from the ptr pointer three times per pixel.
Try this (warning: I have not checked it.)
public void dataAcquired()
{
Bitmap bmp = new Bitmap(width, height);
BitmapData data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
unsafe
{
int i = 0;
byte* ptr = (byte*)data.Scan0;
for( int y = 0; y < bmp.Height; y++ )
{
byte* ptr2 = ptr;
for( int x = 0; x < bmp.Width; x++ )
{
Rgb rgb = mapColors[data[i++]];
*(ptr2++) = rgb.b;
*(ptr2++) = rgb.g;
*(ptr2++) = rgb.r;
}
ptr += data.Stride;
}
}
bmp.UnlockBits(data);
}
Try run code in parallel: change
for (int i = 0; i < data.Length; i++) {
...
}
into
Parallel.For(0, data.Length, (i) => {
int x = i % bmp.Width;
int y = i / bmp.Width;
Rgb rgb = mapColors[data[i]];
unsafe {
byte* ptr = (byte*) data.Scan0;
ptr[(x * 3) + y * data.Stride] = rgb.b;
ptr[(x * 3) + y * data.Stride + 1] = rgb.g;
ptr[(x * 3) + y * data.Stride + 2] = rgb.r;
}
});
I am creating Bitmap object from the same image, but in the end getting different results. It should calculate deviation from the image colors and images are the same so why results are different?
double test1 = GetStdDev("C:\\temp\\images\\file.jpg");
Bitmap img = new Bitmap("C:\\temp\\images\\file.jpg");
double test2 = GetStdDev(img);
public static double GetStdDev(string imageFileName)
{
double total = 0, totalVariance = 0;
int count = 0;
double stdDev = 0;
// First get all the bytes
using (Bitmap b = new Bitmap(imageFileName))
{
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
int stride = bmData.Stride;
IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nOffset = stride - b.Width * 3;
for (int y = 0; y < b.Height; ++y)
{
for (int x = 0; x < b.Width; ++x)
{
count++;
byte blue = p[0];
byte green = p[1];
byte red = p[2];
int pixelValue = Color.FromArgb(0, red, green, blue).ToArgb();
total += pixelValue;
double avg = total / count;
totalVariance += Math.Pow(pixelValue - avg, 2);
stdDev = Math.Sqrt(totalVariance / count);
p += 3;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
}
return stdDev;
}
private static double GetStdDev(Bitmap img)
{
double total = 0, totalVariance = 0;
int count = 0;
double stdDev = 0;
// First get all the bytes
using (Bitmap b = new Bitmap(img))
{
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, b.PixelFormat);
int stride = bmData.Stride;
IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nOffset = stride - b.Width * 3;
for (int y = 0; y < b.Height; ++y)
{
for (int x = 0; x < b.Width; ++x)
{
count++;
byte blue = p[0];
byte green = p[1];
byte red = p[2];
int pixelValue = Color.FromArgb(0, red, green, blue).ToArgb();
total += pixelValue;
double avg = total / count;
totalVariance += Math.Pow(pixelValue - avg, 2);
stdDev = Math.Sqrt(totalVariance / count);
p += 3;
}
p += nOffset;
}
}
b.UnlockBits(bmData);
}
return stdDev;
}
Just found solution, it was problem with deep copying bitmap object. instead using new Bitmap(img) use Bitmap img2 = (Bitmap) img.Clone(); don't know it is the right solution, but it does the job.