Track ball position by image - c#

lets say that i have this image:
http://srv2.jpg.co.il/9/51c614f7c280e.png
i want to get the white ball position(x,y),
this is a verey big image, then i cut the image by rectangle.(because when the image is smaller everything is faster),
the results:
http://srv2.jpg.co.il/1/51c616787a3fa.png
now i want to track the white ball position by his color(white=rbg(255,255,255)),
my code:
Public Function GetBallPosition(ByRef HaxScreenOnly As Bitmap) As Point
For y = 0 To HaxScreenOnly.Height - 1
For x = 0 To HaxScreenOnly.Width - 1
If HaxScreenOnly.GetPixel(x, y) = Color.FromArgb(0, 0, 0) Then
If HaxScreenOnly.GetPixel(x + 8, y) = Color.FromArgb(0, 0, 0) And HaxScreenOnly.GetPixel(x + 8, y + 3) = Color.FromArgb(255, 255, 255) Then
Return New Point(x, y)
End If
End If
Next
Next
Return New Point(0, 0)
End Function
If the color of the current pixel is black and the color of the current pixel(x+8,y+3) is white then this is the ball
it's working...but its verey slow, something like 200 miliseconds to track the ball position.
this is not fast enough.
there is faster way to track the white ball(C# or VB.net)?

Finally, I have you a solution for you. Calling GetPixel is a costly process, but you use Bitmap.LockBits and manipulate / access the image data from a pointer. I took the LockBitmap class from this article.
I checked on the performance from what I was getting previously, which was exactly like you mentioned, around 200ms~.
Here is a picture of the result using LockBitmap, rescanning the image continuously with the optimized code!
static void Main(string[] args)
{
byte[] data = new WebClient().DownloadData("http://srv2.jpg.co.il/1/51c616787a3fa.png");
Image image = Image.FromStream(new MemoryStream(data));
LockBitmap bitmap = new LockBitmap(new Bitmap(image));
// this essentially copies the data into memory and copies from a pointer to an array
bitmap.LockBits();
Color black = Color.FromArgb(0, 0, 0);
Color white = Color.FromArgb(255, 255, 255);
Stopwatch stopwatch = Stopwatch.StartNew();
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
// GetPixel is a nice abstraction the author in the Article created so we don't have to do any of the gritty stuff.
if (bitmap.GetPixel(x, y) == black)
{
if (bitmap.GetPixel(x + 8, y) == black && bitmap.GetPixel(x + 8, y + 3) == white)
{
Console.WriteLine("White Ball Found in {0}", stopwatch.Elapsed.ToString());
break;
}
}
}
}
bitmap.UnlockBits(); // copies the data from the array back to the original pointer
Console.Read();
}
Hope this helps, it was certainly an interesting read for me.
Update:
As mentioned by King, I was able to further reduce the timing for you based on the algorithm improvement. So we've gone from O(n) to O(n log) time complexity (I think).
for (int y = 0; y < bitmap.Height; y += 3) // As we know the radius of the ball
{
for (int x = 0; x < bitmap.Width; x += 3) // We can increase this
{
if (bitmap.GetPixel(x, y) == black && bitmap.GetPixel(x, y + 3) == white)
{
Console.WriteLine("White Ball Found ({0},{1}) in {2}", x, y, stopwatch.Elapsed.ToString());
break;
}
}
}

Related

Approximate image from byte array

I wrote an application where some dots are floating around and if i assign a dot a Point, it will move on this position. Now i want to load an image, convert it to a monochrome image (Only pure black or white pixels - no shades of gray) and make each dot floating to a position where its representing a black pixel.
I've already done loading and converting a image that way and extraceted the pixels as a 1 dimensional byte[]. I've managed to iterate though this array with the following code:
int stride = width * 4;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
int index = y * stride + 4 * x;
// array[index] <- Red
// array[index + 1] <- Green
// array[index + 2] <- Blue
// array[index + 3] <- Alpha
}
The byte array holds every pixel with 4 bytes (RGBA). So the array length is ImageHeight*ImageWidth*4 bytes. Either a pixel is Black (0, 0, 0, 255) or White (255, 255, 255, 255).
My problem now is that i'm not able to correctly approximate the black areas of the image with just n dots. In most cases I will have much less floating dots than there are black pixels in the array. So what i need is a method that gives me a Point[] that contains n Points that will represent only the black areas of the image as good as possible. Can someone help me out?
Loop though the array and find the points that their red, green and blue are 0
to get the Black dots:
List<Point> blackPoints = new List<Points>()
for(int i=0; i<array.Length; i+=4)
if(array[i] == 0 && array[i+1] == 0 && array[i+2] ==0) //alpha is not important
{
int tmp = i / 4;
blackPoints.Add(new Point(tmp%width, tmp/width));
}
Create Methods, to get the weight of a pixel based on its own and neighbors colors, and also a Method to find the weight of a block:
public static class Exts
{
public static int Weight(this points ps, int x, int y, int width, int height)
{
int weight = 0;
for(int i=Math.Max(x - 1, 0); i<Math.Min(width, x+1))
for(int j= Math.Max(y-1, 0), j<Math.Min(height, y+1))
if(ps.Any(a => a.X == i && a.Y == j)) weight++;
return weight;
}
public static int BlockWeight(this Point[] ps, int x, int y)
{
return ps.Count(a => a.X <= x+2 && a.Y<= y+2);
}
}
Now loop through the bitmap, with blocks of nine pixels (3x3) and if a blocks wieght is more than half (in this case more than or equal to 5), select a the point in this block that has heighest weight, to represent the black point:
List<Point> result = new List<Point>();
for(int i=0; i<width; i+=3)
for(int j=0; j< height; j+=3)
if(points.BlockWeight(i,j) >= 5)
result.Add(ps.Where(a => a.X <= x+2 && a.Y<= y+2).OrderByDescending(a => a.Weight(i, j, width, height)).First())

c# winforms gdi+ - Crop an Image to it's contents

I am making a paint application in GDI+ WinForms, and I have a feature I would like to add and have tried to add and I haven't found anything about it online.
I would like to take an Image (or Bitmap, doesn't really matter) and crop it down to where all the content in it is.
Let me give an example:
I have an image here
And it has quite a bit of white around it (Save the image to your computer to see that it has white around it.) I would like to crop the image down to just the area where the stickman is, I would like it to look like this:
.
(Save it to your computer and you can compare the two)
If you look at the second one, which is what I would like to produce, it has cut the image down to just the stickman!
But, of course, I've done that myself.
I've looked online a lot for a solution and couldn't find one, so I decided to try and do it myself, it didn't work.
Here's what I tried:
I have a simple form with a picture box and a button - upon clicking of the button it should crop the image down. I made the picture box's BackColor black and it to center the image so that the areas that aren't in the image anymore were black.
The image is stored in a Bitmap called ImageToChange.
Upon click of the button it should crop the image - so I made a function to do that which I will call from the button
This function depends on another function that I found online to crop the image:
public Bitmap CropImage(Image source, int x, int y, int width, int height)
{
Rectangle crop = new Rectangle(x, y, width, height);
var bmp = new Bitmap(crop.Width, crop.Height);
using (var gr = Graphics.FromImage(bmp))
{
gr.DrawImage(source, new Rectangle(0, 0, bmp.Width, bmp.Height), crop, GraphicsUnit.Pixel);
}
return bmp;
}
The function above should just crop and image to the x, y, width and height given to it - I didn't make the code but I can see what it does.
My CropToContent function depends on that at the end.
So, here is the function I created to crop the image down:
public Bitmap CropToContent(Bitmap oldBmp)
{
Rectangle currentRect = new Rectangle();
// Get a base color
for (int y = 0; y < oldBmp.Height; y++)
{
for (int x = 0; x < oldBmp.Width; x++)
{
if (oldBmp.GetPixel(x, y) != Color.White)
{
// We need to interpret this!
if (!currentRect.Contains(new Point(x, y)))
{
// This will run if this is out of the current rectangle
if (x > (currentRect.X + currentRect.Width)) currentRect.Width += ((currentRect.X + currentRect.Width) + x);
if (x < (currentRect.X))
{
// Move the rectangle over there and extend it's width to make the right the same!
int oldRectLeft = currentRect.Left;
currentRect.X = x;
currentRect.Width += oldRectLeft - x;
}
if (y > (currentRect.Y + currentRect.Height)) currentRect.Height += ((currentRect.Y + currentRect.Height) + y);
if (y < (currentRect.Y + currentRect.Height))
{
int oldRectTop = currentRect.Top;
currentRect.Y = y;
currentRect.Height += oldRectTop - y;
}
}
}
}
}
return CropImage(oldBmp, currentRect.X, currentRect.Y, currentRect.Width, currentRect.Height);
}
As you can see, it uses the CropImage function I mentioned earlier!
This function has a rectangle of where the image will be cropped to - this gets modified as the function goes along.
The function loops through all the pixels in the Bitmap and if it isn't white it ignores it - if it is anything else however it will do a certain depending on where around the rectangle it is,
If it's on the left of the rectangle it will move the X of the rectangle over and change the Width so the right of the rectangle is still the same
If it's on the top of the rectangle it will move the Y of the rectangle up and change the Height...
If it's on the right of the rectangle it will change the Width to match.
If it's on the bottom of the rectangle it will change the Height to match.
And if it's inside the rectangle it won't care at all.
I can't see why this function would not work.
Upon Load of the form it runs this code:
ImageToChange = Properties.Resources.stickman;
pictureBox1.Image = ImageToChange;
And I put the untouched stickman into Properties.Resources.stickman.
Then on click of the button it runs:
ImageToChange = CropToContent(ImageToChange);
pictureBox1.Image = ImageToChange;
I cannot see why this won't work and thank you very much if you read the whole thing.
I was a bit through of by the question with what you mean with "won't work", but I think I found the issue.
The error is in your logic, for instance the line of code which modifies the right side of the rectangle:
if (x > (currentRect.X + currentRect.Width)) currentRect.Width += ((currentRect.X + currentRect.Width) + x);
This modifies the width of the detected rectangle by adding x, rectangle.X and rectangle.Width which is wrong. What you want is probably this:
if (x > (currentRect.X + currentRect.Width)) currentRect.Width = x - currentRect.X;
You will need similar changes to the rest of your logic.
So, the answer has finally come thanks to Hans Passant pointing out that I should step through it and keep an eye on it which a much small image.
And Robin Krom saying that some of the logic was off.
This is the end result:
public Bitmap CropToContent(Bitmap oldBmp)
{
Rectangle currentRect = new Rectangle();
bool IsFirstOne = true;
// Get a base color
for (int y = 0; y < oldBmp.Height; y++)
{
for (int x = 0; x < oldBmp.Width; x++)
{
Color debug = oldBmp.GetPixel(x, y);
if (oldBmp.GetPixel(x, y) != Color.FromArgb(255, 255, 255, 255))
{
// We need to interpret this!
// Check if it is the first one!
if (IsFirstOne)
{
currentRect.X = x;
currentRect.Y = y;
currentRect.Width = 1;
currentRect.Height = 1;
IsFirstOne = false;
}
else
{
if (!currentRect.Contains(new Point(x, y)))
{
// This will run if this is out of the current rectangle
if (x > (currentRect.X + currentRect.Width)) currentRect.Width = x - currentRect.X;
if (x < (currentRect.X))
{
// Move the rectangle over there and extend it's width to make the right the same!
int oldRectLeft = currentRect.Left;
currentRect.X = x;
currentRect.Width += oldRectLeft - x;
}
if (y > (currentRect.Y + currentRect.Height)) currentRect.Height = y - currentRect.Y;
if (y < (currentRect.Y + currentRect.Height))
{
int oldRectTop = currentRect.Top;
currentRect.Y = y;
currentRect.Height += oldRectTop - y;
}
}
}
}
}
}
return CropImage(oldBmp, currentRect.X, currentRect.Y, currentRect.Width, currentRect.Height);
}
I figured out when debugging with a smaller image that on the first pixel I have to set the starting point for the Rectangle - it defaults to 0, 0 and if the first pixel is at 2, 2 it will be on the right of course.
The rectangle extends one and is now at the location of 0, 0 with the width of 1... yeah... that's not correct - it needs to start where the first pixel is, so I added that.
And I of course fixed up the logic thanks to Robin Krom and the function worked perfectly on the stickman!
So it starts like this:
and the result is this:
It's also worth noting that the line if (oldBmp.GetPixel(x, y) != Color.FromArgb(255, 255, 255, 255)) used to be if (oldBmp.GetPixel(x, y) != Color.White) which didn't work, for some reason.

How can i use LockBits with a Bitmap to scan for white pixels and then write to a new bitmap all the non white pixels?

This is what i did in form1 constructor:
Bitmap bmp2 = new Bitmap(#"e:\result1001.jpg");
CropImageWhiteAreas.ImageTrim(bmp2);
bmp2.Save(#"e:\result1002.jpg");
bmp2.Dispose();
The class CropImageWhiteAreas:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace Test
{
class CropImageWhiteAreas
{
public static Bitmap ImageTrim(Bitmap img)
{
//get image data
BitmapData bd = img.LockBits(new Rectangle(Point.Empty, img.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int[] rgbValues = new int[img.Height * img.Width];
Marshal.Copy(bd.Scan0, rgbValues, 0, rgbValues.Length);
img.UnlockBits(bd);
#region determine bounds
int left = bd.Width;
int top = bd.Height;
int right = 0;
int bottom = 0;
//determine top
for (int i = 0; i < rgbValues.Length; i++)
{
int color = rgbValues[i] & 0xffffff;
if (color != 0xffffff)
{
int r = i / bd.Width;
int c = i % bd.Width;
if (left > c)
{
left = c;
}
if (right < c)
{
right = c;
}
bottom = r;
top = r;
break;
}
}
//determine bottom
for (int i = rgbValues.Length - 1; i >= 0; i--)
{
int color = rgbValues[i] & 0xffffff;
if (color != 0xffffff)
{
int r = i / bd.Width;
int c = i % bd.Width;
if (left > c)
{
left = c;
}
if (right < c)
{
right = c;
}
bottom = r;
break;
}
}
if (bottom > top)
{
for (int r = top + 1; r < bottom; r++)
{
//determine left
for (int c = 0; c < left; c++)
{
int color = rgbValues[r * bd.Width + c] & 0xffffff;
if (color != 0xffffff)
{
if (left > c)
{
left = c;
break;
}
}
}
//determine right
for (int c = bd.Width - 1; c > right; c--)
{
int color = rgbValues[r * bd.Width + c] & 0xffffff;
if (color != 0xffffff)
{
if (right < c)
{
right = c;
break;
}
}
}
}
}
int width = right - left + 1;
int height = bottom - top + 1;
#endregion
//copy image data
int[] imgData = new int[width * height];
for (int r = top; r <= bottom; r++)
{
Array.Copy(rgbValues, r * bd.Width + left, imgData, (r - top) * width, width);
}
//create new image
Bitmap newImage = new Bitmap(width, height, PixelFormat.Format32bppArgb);
BitmapData nbd
= newImage.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(imgData, 0, nbd.Scan0, imgData.Length);
newImage.UnlockBits(nbd);
return newImage;
}
}
}
I also tried before it Peter solution.
In both the result is(This is a screenshot of my facebook after uploaded the image) still the white areas around:
You can the rectangle around the image i just uploaded and see what i mean by white area around.
If I understand correctly, you have found a sample code snippet that uses LockBits(), but you are not sure how it works or how to modify it to suit your specific need. So I will try to answer from that perspective.
First, a wild guess (since you didn't include the implementation of the LockBitmap class you're using in the first example): the LockBitmap class is some kind of helper class that is supposed to encapsulate the work of calling LockBits() and using the result, including providing versions of GetPixel() and SetPixel() which are presumably much faster than calling those methods on a Bitmap object directly (i.e. access the buffer obtained by calling LockBits()).
If that's the case, then modifying the first example to suit your need is probably best:
public void Change(Bitmap bmp)
{
Bitmap newBitmap = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat);
LockBitmap source = new LockBitmap(bmp),
target = new LockBitmap(newBitmap);
source.LockBits();
target.LockBits();
Color white = Color.FromArgb(255, 255, 255, 255);
for (int y = 0; y < source.Height; y++)
{
for (int x = 0; x < source.Width; x++)
{
Color old = source.GetPixel(x, y);
if (old != white)
{
target.SetPixel(x, y, old);
}
}
}
source.UnlockBits();
target.UnlockBits();
newBitmap.Save("d:\\result.png");
}
In short: copy the current pixel value to a local variable, compare that value to the white color value, and if it is not the same, go ahead and copy the pixel value to the new bitmap.
Some variation on the second code example should work as well. The second code example does explicitly what is (I've assumed) encapsulated inside the LockBitmap class that the first code example uses. If for some reason, the first approach isn't suitable for your needs, you can follow the second example.
In that code example you provide, most of the method there is just handling the "grunt work" of locking the bitmap so that the raw data can be accessed, and then iterating through that raw data.
It computes the oRow and nRow array offsets (named for "old row" and "new row", I presume) based on the outer y loop, and then accesses individual pixel data by computing the offset within a given row based on the inner x loop.
Since you want to do essentially the same thing, but instead of converting the image to grayscale, you just want to selectively copy all non-white pixels to the new bitmap, you can (should be able to) simply modify the body of the inner x loop. For example:
byte red = oRow[x * pixelSize + 2],
green = oRow[x * pixelSize + 1],
blue = oRow[x * pixelSize];
if (red != 255 || green != 255 || blue != 255)
{
nRow[x * pixelSize + 2] = red;
nRow[x * pixelSize + 1] = green;
nRow[x * pixelSize] = blue;
}
The above would entirely replace the body of the inner x loop.
One caveat: do note that when using the LockBits() approach, knowing the pixel format of the bitmap is crucial. The example you've shown assumes the bitmaps are in 24 bpp format. If your own bitmaps are in this format, then you don't need to change anything. But if they are in a different format, you'll need to adjust the code to suit that. For example, if your bitmap is in 32 bpp format, you need to pass the correct PixelFormat value to the LockBits() method calls, and then set pixelSize to 4 instead of 3 as the code does now.
Edit:
You've indicated that you would like to crop the new image so that it is the minimize size required to contain all of the non-white pixels. Here is a version of the first example above that should accomplish that:
public void Change(Bitmap bmp)
{
LockBitmap source = new LockBitmap(bmp);
source.LockBits();
Color white = Color.FromArgb(255, 255, 255, 255);
int minX = int.MaxValue, maxX = int.MinValue,
minY = int.MaxValue, maxY = int.MinValue;
// Brute-force scan of the bitmap to find image boundary
for (int y = 0; y < source.Height; y++)
{
for (int x = 0; x < source.Width; x++)
{
if (source.GetPixel(x, y) != white)
{
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
}
}
Bitmap newBitmap = new Bitmap(maxX - minx + 1, maxY - minY + 1, bmp.PixelFormat);
LockBitmap target = new LockBitmap(newBitmap);
target.LockBits();
for (int y = 0; y < target.Height; y++)
{
for (int x = 0; x < target.Width; x++)
{
target.SetPixel(x, y, source.GetPixel(x + minX, y + minY));
}
}
source.UnlockBits();
target.UnlockBits();
newBitmap.Save("d:\\result.png");
}
This example includes an initial scan of the original bitmap, after locking it, to find the minimum and maximum coordinate values for any non-white pixel. Having done that, it uses the results of that scan to determine the dimensions of the new bitmap. When copying the pixels, it restricts the x and y loops to the dimensions of the new bitmap, adjusting the x and y values to map from the location in the new bitmap to the given pixel's original location in the old one.
Note that since the initial scan determines where the non-white pixels are, there's no need to check again when actually copying the pixels.
There are more efficient ways to scan the bitmap than the above. This version simply looks at every single pixel in the original bitmap, keeping track of the min and max values for each coordinate. I'm guessing this will be fast enough for your purposes, but if you want something faster, you can change the scan so that it scans for each min and max in sequence:
Scan each row from y of 0 to determine the first row with a non-white pixel. This is the min y value.
Scan each row from y of source.Height - 1 backwards, to find the max y value.
Having found the min and max y values, now scan the columns from x of 0 to find the min x and from source.Width - 1 backwards to find the max x.
Doing it that way involves a lot more code and is probably harder to read and understand, but would involve inspecting many fewer pixels in most cases.
Edit #2:
Here is a sample of the output of the second code example:
Note that all of the white border of the original bitmap (shown on the left side) has been cropped out, leaving only the smallest subset of the original bitmap that can contain all of the non-white pixels (shown on the right side).

region growing image segmentation

i make region growing algorithm for my project
this is my algorithm
(my picture have been greyscale before it)
1. get value pixel (0,0) for seed pixel
2. compare value seed pixel with one neighbor pixel
3. if value of no.3 less than treshold (T), go to next pixel and go to no.2
4. if value of no.3 more than treshold (T), change pixel to white(also for next 10 pixel), and get new seed value pixel.
my goal is my picture segmented with white line
this is my code
private void button4_Click(object sender, EventArgs e)
{
// GDI+ still lies to us - the return format is BGR, NOT RGB.
BitmapData bmData = RImage.LockBits(new Rectangle(0, 0, RImage.Width, RImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nOffset = stride - RImage.Width * 3;
for (int y = 0; y < RImage.Height; ++y)
{
for (int x = 0; x < RImage.Width; ++x)
{
//every new line of x must new seed
if (x == 0)
{
//getting new value seed pixel
seedR = p[x];
seedG = p[x+1];
seedB = p[x+2];
}
//compare value of seed pixel and pixel scan
if ((seedR - p[x] >= tred) || (p[x] - seedR >= tred))
{
//make white line with change value of pixel
for (int i=1; i <= 5; ++i)
{
p[x] = p[x + 1] = p[x + 2] = 0;
x++;
}
//getting new value of seed pixel
seedR = p[x];
seedG = p[x + 1];
seedB = p[x + 2];
}
p += 3;
}
p += nOffset;
}
}
RImage.UnlockBits(bmData);
}
my problem is my image become white in 1/3 of image
what must i doing for "region growing" ??
thx
I've left some questions about your algorithm in the comments, but as I was writing them I realized that what you're trying to do may not be image segmentation at all.
my goal is my picture segmented with white line
Do you mean you want something like this:
If yes, then what you're interested in isn't image segmentation, it's edge detection. If you want to implement something like that, then have a read about convolution as well.

c# GDI Edge Whitespace Detection Algorithm

I am looking for a solution for detecting edge whitespace of c# bitmap, from the c# managed GDI+ library.
The images would be either transparent or white, most of the 400x pictures are 8000x8000px with about 2000px whitespace around the edges.
What would be the most efficient way of finding out the edges, x, y, height and width coordinates? I tried a go pixel by pixel but was finding it very slow.
Update to solution
--Added left/right/top/bottom bounds
Problems with images detail center images, now crops any transparent (0%) or white (#FFFFFF) pixels.
var top = bitmap.Height;
var left = bitmap.Width;
var right = 0;
var bottom = 0;
...
var pData = pData0 + (y * data.Stride) + (x * 4);
var xyAlpha = pData[3];
var xyBlue = pData[0];
var xyGreen = pData[1];
var xyRed = pData[2];
if ((xyAlpha > 0) || (xyRed != 255 && xyGreen != 255 && xyBlue != 255)) {
if (y < top)
top = y;
if (y > bottom)
bottom = y;
if (x < left)
left = x;
if (x > right)
right = x;
}
...
var cropWidth = right - left;
var cropHeight = bottom - top;
var cropX = top;
var cropY = left;
var cacheBitmap = new Bitmap(cropWidth, cropHeight, PixelFormat.Format32bppArgb);
using (var cacheGraphics = Graphics.FromImage(cacheBitmap)) {
cacheGraphics.DrawImage(context.Image, new Rectangle(0, 0, cropWidth, cropHeight), cropX, cropY, cropWidth, cropHeight, GraphicsUnit.Pixel);
}
A great GDI+ resource is Bob Powell's GDI+ FAQ!
You didn't say how you accessed the pixels in the image so I will assume that you used the slow GetPixel methods. You can use pointers and LockBits to access pixels in a faster way: see Bob Powells explanation of LockBits
This will require an unsafe code block - if you don't want this or you do not have FullTrust you can use the trick explained here: Pointerless Image Processing in .NET by J. Dunlap
The below code uses the LockBits approach (for the PixelFormat.Format32bppArgb) and will fill the start and end Points with the value where the first and last pixels in an image are discovered that do not have the color described in the argument color. The method also ignores completely transparent pixels which is useful if you want to detect the area of an image where the visible 'content' starts.
Point start = Point.Empty;
Point end = Point.Empty;
int bitmapWidth = bmp.Width;
int bitmapHeight = bmp.Height;
#region find start and end point
BitmapData data = bmp.LockBits(new Rectangle(0, 0, bitmapWidth, bitmapHeight), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try
{
unsafe
{
byte* pData0 = (byte*)data.Scan0;
for (int y = 0; y < bitmapHeight; y++)
{
for (int x = 0; x < bitmapWidth; x++)
{
byte* pData = pData0 + (y * data.Stride) + (x * 4);
byte xyBlue = pData[0];
byte xyGreen = pData[1];
byte xyRed = pData[2];
byte xyAlpha = pData[3];
if (color.A != xyAlpha
|| color.B != xyBlue
|| color.R != xyRed
|| color.G != xyGreen)
{
//ignore transparent pixels
if (xyAlpha == 0)
continue;
if (start.IsEmpty)
{
start = new Point(x, y);
}
else if (start.Y > y)
{
start.Y = y;
}
if (end.IsEmpty)
{
end = new Point(x, y);
}
else if (end.X < x)
{
end.X = x;
}
else if (end.Y < y)
{
end.Y = y;
}
}
}
}
}
}
finally
{
bmp.UnlockBits(data);
}
#endregion
I'd first make sure to use the LockBits method described by Patrick. Second I'd check the pixels on the middle lines to quickly determine the edges. By middle lines I mean, if you have say for example a 2000x1000 image, you'd look first along horizontal line number 500 (out of 1000) to find the left and right limits, then along vertical line number 1000 (out of 2000) to find the top and bottom limits. It should be very fast this way.

Categories

Resources