Creating a GraphicsPath from a semi-transparent bitmap - c#

I want to create a GraphicsPath and a list of Points to form the outline of the non-transparent area of a bitmap. If needed, I can guarantee that each image has only one solid collection of nontransparent pixels. So for example, I should be able to record the points either clockwise or counter-clockwise along the edge of the pixels and perform a full closed loop.
The speed of this algorithm is not important. However, the efficiency of the resulting points is semi-important if I can skip some points to reduce in a smaller and less complex GraphicsPath.
I will list my current code below which works perfectly with most images. However, some images which are more complex end up with paths which seem to connect in the wrong order. I think I know why this occurs, but I can't come up with a solution.
public static Point[] GetOutlinePoints(Bitmap image)
{
List<Point> outlinePoints = new List<Point>();
BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] originalBytes = new byte[image.Width * image.Height * 4];
Marshal.Copy(bitmapData.Scan0, originalBytes, 0, originalBytes.Length);
for (int x = 0; x < bitmapData.Width; x++)
{
for (int y = 0; y < bitmapData.Height; y++)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
outlinePoints.Add(p);
break;
}
}
}
for (int y = 0; y < bitmapData.Height; y++)
{
for (int x = bitmapData.Width - 1; x >= 0; x--)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
outlinePoints.Add(p);
break;
}
}
}
for (int x = bitmapData.Width - 1; x >= 0; x--)
{
for (int y = bitmapData.Height - 1; y >= 0; y--)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
outlinePoints.Add(p);
break;
}
}
}
for (int y = bitmapData.Height - 1; y >= 0; y--)
{
for (int x = 0; x < bitmapData.Width; x++)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
outlinePoints.Add(p);
break;
}
}
}
// Added to close the loop
outlinePoints.Add(outlinePoints[0]);
image.UnlockBits(bitmapData);
return outlinePoints.ToArray();
}
public static bool ContainsPoint(IEnumerable<Point> points, Point value)
{
foreach (Point p in points)
{
if (p == value)
return true;
}
return false;
}
And when I turn the points into a path:
GraphicsPath outlinePath = new GraphicsPath();
outlinePath.AddLines(_outlinePoints);
Here's an example showing what I want. The red outline should be an array of points which can be made into a GraphicsPath in order to perform hit detection, draw an outline pen, and fill it with a brush.

Like both of you desrcipt you just have to find the first non transparent point and afterward move along the none transparent pixels with a transparent neighbor.
Additionaly you'll have to save the point you've already visisted and how often you visited them or you'll end in same cases in an invinity loop. If the point doesn't have a neighbor which already was visited you must go back each point, in revered direction, until a unvisited point is available again.
That's it.
See revision history for old versions.
Changes:
Point.EMPTY was replaced by Point(-1,-1), or a non transparent pixel
in the upper left corner causes an invinityloop
Check for borderpoint at the image border
class BorderFinder {
int stride = 0;
int[] visited = null;
byte[] bytes = null;
PointData borderdata = null;
Size size = Size.Empty;
bool outside = false;
Point zeropoint = new Point(-1,-1);
public List<Point[]> Find(Bitmap bmp, bool outside = true) {
this.outside = outside;
List<Point> border = new List<Point>();
BitmapData bmpdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
stride = bmpdata.Stride;
bytes = new byte[bmp.Width * bmp.Height * 4];
size = bmp.Size;
Marshal.Copy(bmpdata.Scan0, bytes, 0, bytes.Length);
// Get all Borderpoint
borderdata = getBorderData(bytes);
bmp.UnlockBits(bmpdata);
List<List<Point>> regions = new List<List<Point>>();
//Loop until no more borderpoints are available
while (borderdata.PointCount > 0) {
List<Point> region = new List<Point>();
//if valid is false the region doesn't close
bool valid = true;
//Find the first borderpoint from where whe start crawling
Point startpos = getFirstPoint(borderdata);
//we need this to know if and how often we already visted the point.
//we somtime have to visit a point a second time because we have to go backward until a unvisted point is found again
//for example if we go int a narrow 1px hole
visited = new int[bmp.Size.Width * bmp.Size.Height];
region.Add(startpos);
//Find the next possible point
Point current = getNextPoint(startpos);
if (current != zeropoint) {
visited[current.Y * bmp.Width + current.X]++;
region.Add(current);
}
//May occure with just one transparent pixel without neighbors
if (current == zeropoint)
valid = false;
//Loop until the area closed or colsing the area wasn't poosible
while (!current.Equals(startpos) && valid) {
var pos = current;
//Check if the area was aready visited
if (visited[current.Y * bmp.Width + current.X] < 2) {
current = getNextPoint(pos);
visited[pos.Y * bmp.Width + pos.X]++;
//If no possible point was found, search in reversed direction
if (current == zeropoint)
current = getNextPointBackwards(pos);
} else { //If point was already visited, search in reversed direction
current = getNextPointBackwards(pos);
}
//No possible point was found. Closing isn't possible
if (current == zeropoint) {
valid = false;
break;
}
visited[current.Y * bmp.Width + current.X]++;
region.Add(current);
}
//Remove point from source borderdata
foreach (var p in region) {
borderdata.SetPoint(p.Y * bmp.Width + p.X, false);
}
//Add region if closing was possible
if (valid)
regions.Add(region);
}
//Checks if Region goes the same way back and trims it in this case
foreach (var region in regions) {
int duplicatedpos = -1;
bool[] duplicatecheck = new bool[size.Width * size.Height];
int length = region.Count;
for (int i = 0; i < length; i++) {
var p = region[i];
if (duplicatecheck[p.Y * size.Width + p.X]) {
duplicatedpos = i - 1;
break;
}
duplicatecheck[p.Y * size.Width + p.X] = true;
}
if (duplicatedpos == -1)
continue;
if (duplicatedpos != ((region.Count - 1) / 2))
continue;
bool reversed = true;
for (int i = 0; i < duplicatedpos; i++) {
if (region[duplicatedpos - i - 1] != region[duplicatedpos + i + 1]) {
reversed = false;
break;
}
}
if (!reversed)
continue;
region.RemoveRange(duplicatedpos + 1, region.Count - duplicatedpos - 1);
}
List<List<Point>> tempregions = new List<List<Point>>(regions);
regions.Clear();
bool connected = true;
//Connects region if possible
while (connected) {
connected = false;
foreach (var region in tempregions) {
int connectionpos = -1;
int connectionregion = -1;
Point pointstart = region.First();
Point pointend = region.Last();
for (int ir = 0; ir < regions.Count; ir++) {
var otherregion = regions[ir];
if (region == otherregion)
continue;
for (int ip = 0; ip < otherregion.Count; ip++) {
var p = otherregion[ip];
if ((isConnected(pointstart, p) && isConnected(pointend, p)) ||
(isConnected(pointstart, p) && isConnected(pointstart, p))) {
connectionregion = ir;
connectionpos = ip;
}
if ((isConnected(pointend, p) && isConnected(pointend, p))) {
region.Reverse();
connectionregion = ir;
connectionpos = ip;
}
}
}
if (connectionpos == -1) {
regions.Add(region);
} else {
regions[connectionregion].InsertRange(connectionpos, region);
}
}
tempregions = new List<List<Point>>(regions);
regions.Clear();
}
List<Point[]> returnregions = new List<Point[]>();
foreach (var region in tempregions)
returnregions.Add(region.ToArray());
return returnregions;
}
private bool isConnected(Point p0, Point p1) {
if (p0.X == p1.X && p0.Y - 1 == p1.Y)
return true;
if (p0.X + 1 == p1.X && p0.Y - 1 == p1.Y)
return true;
if (p0.X + 1 == p1.X && p0.Y == p1.Y)
return true;
if (p0.X + 1 == p1.X && p0.Y + 1 == p1.Y)
return true;
if (p0.X == p1.X && p0.Y + 1 == p1.Y)
return true;
if (p0.X - 1 == p1.X && p0.Y + 1 == p1.Y)
return true;
if (p0.X - 1 == p1.X && p0.Y == p1.Y)
return true;
if (p0.X - 1 == p1.X && p0.Y - 1 == p1.Y)
return true;
return false;
}
private Point getNextPoint(Point pos) {
if (pos.Y > 0) {
int x = pos.X;
int y = pos.Y - 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
}
}
if (pos.Y > 0 && pos.X < size.Width - 1) {
int x = pos.X + 1;
int y = pos.Y - 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
}
}
if (pos.X < size.Width - 1) {
int x = pos.X + 1;
int y = pos.Y;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
}
}
if (pos.X < size.Width - 1 && pos.Y < size.Height - 1) {
int x = pos.X + 1;
int y = pos.Y + 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
}
}
if (pos.Y < size.Height - 1) {
int x = pos.X;
int y = pos.Y + 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
}
}
if (pos.Y < size.Height - 1 && pos.X > 0) {
int x = pos.X - 1;
int y = pos.Y + 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
}
}
if (pos.X > 0) {
int x = pos.X - 1;
int y = pos.Y;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
}
}
if (pos.X > 0 && pos.Y > 0) {
int x = pos.X - 1;
int y = pos.Y - 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
}
}
return zeropoint;
}
private Point getNextPointBackwards(Point pos) {
Point backpoint = zeropoint;
int trys = 0;
if (pos.X > 0 && pos.Y > 0) {
int x = pos.X - 1;
int y = pos.Y - 1;
if (ValidPoint(x, y) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
if (backpoint == zeropoint || trys > visited[y * size.Width + x]) {
backpoint = new Point(x, y);
trys = visited[y * size.Width + x];
}
}
}
if (pos.X > 0) {
int x = pos.X - 1;
int y = pos.Y;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
if (backpoint == zeropoint || trys > visited[y * size.Width + x]) {
backpoint = new Point(x, y);
trys = visited[y * size.Width + x];
}
}
}
if (pos.Y < size.Height - 1 && pos.X > 0) {
int x = pos.X - 1;
int y = pos.Y + 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
if (backpoint == zeropoint || trys > visited[y * size.Width + x]) {
backpoint = new Point(x, y);
trys = visited[y * size.Width + x];
}
}
}
if (pos.Y < size.Height - 1) {
int x = pos.X;
int y = pos.Y + 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
if (backpoint == zeropoint || trys > visited[y * size.Width + x]) {
backpoint = new Point(x, y);
trys = visited[y * size.Width + x];
}
}
}
if (pos.X < size.Width - 1 && pos.Y < size.Height - 1) {
int x = pos.X + 1;
int y = pos.Y + 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
if (backpoint == zeropoint || trys > visited[y * size.Width + x]) {
backpoint = new Point(x, y);
trys = visited[y * size.Width + x];
}
}
}
if (pos.X < size.Width - 1) {
int x = pos.X + 1;
int y = pos.Y;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
if (backpoint == zeropoint || trys > visited[y * size.Width + x]) {
backpoint = new Point(x, y);
trys = visited[y * size.Width + x];
}
}
}
if (pos.Y > 0 && pos.X < size.Width - 1) {
int x = pos.X + 1;
int y = pos.Y - 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
if (backpoint == zeropoint || trys > visited[y * size.Width + x]) {
backpoint = new Point(x, y);
trys = visited[y * size.Width + x];
}
}
}
if (pos.Y > 0) {
int x = pos.X;
int y = pos.Y - 1;
if ((ValidPoint(x, y)) && HasNeighbor(x, y)) {
if (visited[y * size.Width + x] == 0) {
return new Point(x, y);
}
if (backpoint == zeropoint || trys > visited[y * size.Width + x]) {
backpoint = new Point(x, y);
trys = visited[y * size.Width + x];
}
}
}
return backpoint;
}
private bool ValidPoint(int x, int y) {
return (borderdata[y * size.Width + x]);
}
private bool HasNeighbor(int x, int y) {
if (y > 0) {
if (!borderdata[(y - 1) * size.Width + x]) {
return true;
}
} else if (ValidPoint(x, y)) {
return true;
}
if (x < size.Width - 1) {
if (!borderdata[y * size.Width + (x + 1)]) {
return true;
}
} else if (ValidPoint(x, y)) {
return true;
}
if (y < size.Height - 1) {
if (!borderdata[(y + 1) * size.Width + x]) {
return true;
}
} else if (ValidPoint(x, y)) {
return true;
}
if (x > 0) {
if (!borderdata[y * size.Width + (x - 1)]) {
return true;
}
} else if (ValidPoint(x, y)) {
return true;
}
return false;
}
private Point getFirstPoint(PointData data) {
Point startpos = zeropoint;
for (int y = 0; y < size.Height; y++) {
for (int x = 0; x < size.Width; x++) {
if (data[y * size.Width + x]) {
startpos = new Point(x, y);
return startpos;
}
}
}
return startpos;
}
private PointData getBorderData(byte[] bytes) {
PointData isborderpoint = new PointData(size.Height * size.Width);
bool prevtrans = false;
bool currenttrans = false;
for (int y = 0; y < size.Height; y++) {
prevtrans = false;
for (int x = 0; x <= size.Width; x++) {
if (x == size.Width) {
if (!prevtrans) {
isborderpoint.SetPoint(y * size.Width + x - 1, true);
}
continue;
}
currenttrans = bytes[y * stride + x * 4 + 3] == 0;
if (x == 0 && !currenttrans)
isborderpoint.SetPoint(y * size.Width + x, true);
if (prevtrans && !currenttrans)
isborderpoint.SetPoint(y * size.Width + x - 1, true);
if (!prevtrans && currenttrans && x != 0)
isborderpoint.SetPoint(y * size.Width + x, true);
prevtrans = currenttrans;
}
}
for (int x = 0; x < size.Width; x++) {
prevtrans = false;
for (int y = 0; y <= size.Height; y++) {
if (y == size.Height) {
if (!prevtrans) {
isborderpoint.SetPoint((y - 1) * size.Width + x, true);
}
continue;
}
currenttrans = bytes[y * stride + x * 4 + 3] == 0;
if(y == 0 && !currenttrans)
isborderpoint.SetPoint(y * size.Width + x, true);
if (prevtrans && !currenttrans)
isborderpoint.SetPoint((y - 1) * size.Width + x, true);
if (!prevtrans && currenttrans && y != 0)
isborderpoint.SetPoint(y * size.Width + x, true);
prevtrans = currenttrans;
}
}
return isborderpoint;
}
}
class PointData {
bool[] points = null;
int validpoints = 0;
public PointData(int length) {
points = new bool[length];
}
public int PointCount {
get {
return validpoints;
}
}
public void SetPoint(int pos, bool state) {
if (points[pos] != state) {
if (state)
validpoints++;
else
validpoints--;
}
points[pos] = state;
}
public bool this[int pos] {
get {
return points[pos];
}
}
}

I've modified GetOutlinePoints by adding a helper variable than verifies the position at which new points should be added.
The idea of the outline detection algorithm in your code is something like looking at the image while standing each of its edges and writing down all non-transparent pixels that are visible. It's ok, however, you always added pixels to the end of the collection, which caused issues. I've added a variable that remembers position of the last pixel visible from the previous edge and the current one and uses it to determine index where the new pixel should be added. I suppose it should work as long as the outline is continuous, but I suppose it is in your case.
I've tested it on several images and it seems to work correctly:
public static Point[] GetOutlinePoints(Bitmap image)
{
List<Point> outlinePoints = new List<Point>();
BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] originalBytes = new byte[image.Width * image.Height * 4];
Marshal.Copy(bitmapData.Scan0, originalBytes, 0, originalBytes.Length);
//find non-transparent pixels visible from the top of the image
for (int x = 0; x < bitmapData.Width; x++)
{
for (int y = 0; y < bitmapData.Height; y++)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
outlinePoints.Add(p);
break;
}
}
}
//helper variable for storing position of the last pixel visible from both sides
//or last inserted pixel
int? lastInsertedPosition = null;
//find non-transparent pixels visible from the right side of the image
for (int y = 0; y < bitmapData.Height; y++)
{
for (int x = bitmapData.Width - 1; x >= 0; x--)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
{
if (lastInsertedPosition.HasValue)
{
outlinePoints.Insert(lastInsertedPosition.Value + 1, p);
lastInsertedPosition += 1;
}
else
{
outlinePoints.Add(p);
}
}
else
{
//save last common pixel from visible from more than one sides
lastInsertedPosition = outlinePoints.IndexOf(p);
}
break;
}
}
}
lastInsertedPosition = null;
//find non-transparent pixels visible from the bottom of the image
for (int x = bitmapData.Width - 1; x >= 0; x--)
{
for (int y = bitmapData.Height - 1; y >= 0; y--)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
{
if (lastInsertedPosition.HasValue)
{
outlinePoints.Insert(lastInsertedPosition.Value + 1, p);
lastInsertedPosition += 1;
}
else
{
outlinePoints.Add(p);
}
}
else
{
//save last common pixel from visible from more than one sides
lastInsertedPosition = outlinePoints.IndexOf(p);
}
break;
}
}
}
lastInsertedPosition = null;
//find non-transparent pixels visible from the left side of the image
for (int y = bitmapData.Height - 1; y >= 0; y--)
{
for (int x = 0; x < bitmapData.Width; x++)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
{
if (lastInsertedPosition.HasValue)
{
outlinePoints.Insert(lastInsertedPosition.Value + 1, p);
lastInsertedPosition += 1;
}
else
{
outlinePoints.Add(p);
}
}
else
{
//save last common pixel from visible from more than one sides
lastInsertedPosition = outlinePoints.IndexOf(p);
}
break;
}
}
}
// Added to close the loop
outlinePoints.Add(outlinePoints[0]);
image.UnlockBits(bitmapData);
return outlinePoints.ToArray();
}
Update:
This algorithm won't work correctly for images that have outline parts not "visible" from any of the edges. See comments for suggested solutions. I'll try to post a code snippet later.
Update II
I've prepared another algorithm as described in my comments. It just crawls around the object and saves the outline.
First, it finds the first pixel of the outline using a method from the previous algorithm. Then, it looks throught the neighbouring pixels in clockwise order, find the first one that is transparent and then continues browsing, but looking for a non-transparent one. The first non-transparent pixel found is the next one in the outline. The loop continues until it goes around the whole object and gets back to the starting pixel.
public static Point[] GetOutlinePointsNEW(Bitmap image)
{
List<Point> outlinePoints = new List<Point>();
BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Point currentP = new Point(0, 0);
Point firstP = new Point(0, 0);
byte[] originalBytes = new byte[image.Width * image.Height * 4];
Marshal.Copy(bitmapData.Scan0, originalBytes, 0, originalBytes.Length);
//find non-transparent pixels visible from the top of the image
for (int x = 0; x < bitmapData.Width && outlinePoints.Count == 0; x++)
{
for (int y = 0; y < bitmapData.Height && outlinePoints.Count == 0; y++)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
outlinePoints.Add(p);
currentP = p;
firstP = p;
break;
}
}
}
Point[] neighbourPoints = new Point[] { new Point(-1, -1), new Point(0, -1), new Point(1, -1),
new Point(1, 0), new Point(1, 1), new Point(0, 1),
new Point(-1, 1), new Point(-1, 0) };
//crawl around the object and look for the next pixel of the outline
do
{
bool transparentNeighbourFound = false;
bool nextPixelFound = false;
int i;
//searching is done in clockwise order
for (i = 0; (i < neighbourPoints.Length * 2) && !nextPixelFound; ++i)
{
int neighbourPosition = i % neighbourPoints.Length;
int x = currentP.X + neighbourPoints[neighbourPosition].X;
int y = currentP.Y + neighbourPoints[neighbourPosition].Y;
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
//a transparent pixel has to be found first
if (!transparentNeighbourFound)
{
if (alpha == 0)
{
transparentNeighbourFound = true;
}
}
else //after a transparent pixel is found, a next non-transparent one is the next pixel of the outline
{
if (alpha != 0)
{
Point p = new Point(x, y);
currentP = p;
outlinePoints.Add(p);
nextPixelFound = true;
}
}
}
} while (currentP != firstP);
image.UnlockBits(bitmapData);
return outlinePoints.ToArray();
}
One thing to remember is that it does work IF object does NOT ends at the edge of the image (there has to be a transparent space between the object and each of the edges).
This can be easily done if you just make the image one line larger at each side before passing it to the GetOutlinePointsNEW method.

The scenario by the poster was one I just encountered. After applying the GetOutlinePointsNEW method above, I encountered an index issue when the non-transparent pixel is at the edge of the image causing the crawl to be outside the bounds of the image. Below is a managed code update that treats the out of bounds pixels as non-transparent when performing the crawl.
static public Point[] crawlerPoints = new Point[] { new Point(-1, -1), new Point(0, -1), new Point(1, -1),
new Point(1, 0), new Point(1, 1), new Point(0, 1),
new Point(-1, 1), new Point(-1, 0) };
private BitmapData _bitmapData;
private byte[] _originalBytes;
private Bitmap _bitmap; //this is loaded from an image passed in during processing
public Point[] GetOutlinePoints()
{
List<Point> outlinePoints = new List<Point>();
_originalBytes = new byte[_bitmap.Width * _bitmap.Height * 4];
_bitmapData = _bitmap.LockBits(new Rectangle(0, 0, _bitmap.Width, _bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(_bitmapData.Scan0, _originalBytes, 0, _originalBytes.Length);
GetFirstNonTransparentPoint(outlinePoints);
if (outlinePoints.Count > 0) { GetNonTransparentPoints(outlinePoints); }
_bitmap.UnlockBits(_bitmapData);
return outlinePoints.ToArray();
}
private void GetFirstNonTransparentPoint(List<Point> outlinePoints)
{
Point firstPoint = new Point(0, 0);
for (int x = 0; x < _bitmapData.Width; x++)
{
for (int y = 0; y < _bitmapData.Height; y++)
{
if (!IsPointTransparent(x, y))
{
firstPoint = new Point(x, y);
outlinePoints.Add(firstPoint);
break;
}
}
if (outlinePoints.Count > 0) { break; }
}
}
private void GetNonTransparentPoints(List<Point> outlinePoints)
{
Point currentPoint = outlinePoints[0];
do //Crawl counter clock-wise around the current point
{
bool firstTransparentNeighbourFound = false;
bool nextPixelFound = false;
for (int i = 0; (i < ApplicationVariables.crawlerPoints.Length * 2) && !nextPixelFound; ++i)
{
int crawlPosition = i % ApplicationVariables.crawlerPoints.Length;
if (!firstTransparentNeighbourFound) { firstTransparentNeighbourFound = IsCrawlPointTransparent(crawlPosition, currentPoint); }
else
{
if (!IsCrawlPointTransparent(crawlPosition, currentPoint))
{
outlinePoints.Add(new Point(currentPoint.X + ApplicationVariables.crawlerPoints[crawlPosition].X, currentPoint.Y + ApplicationVariables.crawlerPoints[crawlPosition].Y));
currentPoint = outlinePoints[outlinePoints.Count - 1];
nextPixelFound = true;
}
}
}
} while (currentPoint != outlinePoints[0]);
}
private bool IsCrawlPointTransparent(int crawlPosition, Point currentPoint)
{
int x = currentPoint.X + ApplicationVariables.crawlerPoints[crawlPosition].X;
int y = currentPoint.Y + ApplicationVariables.crawlerPoints[crawlPosition].Y;
if (IsCrawlInBounds(x, y)) { return IsPointTransparent(x, y); }
return true;
}
private bool IsCrawlInBounds(int x, int y)
{
return ((x >= 0 & x < _bitmapData.Width) && (y >= 0 & y < _bitmapData.Height));
}
private bool IsPointTransparent(int x, int y)
{
return _originalBytes[(y * _bitmapData.Stride) + (4 * x) + 3] == 0;
}

Related

How to Draw a Outline (Edge ) of Irregular Shape using c#

enter image description here
Please give me a solution about that I have ti find out many site's but i did not get any solution.
thankyou
private void Form1_Load(object sender, EventArgs e)
{
Bitmap bitmap = new Bitmap(#"D:\shapes_Images\4.png");
pictureBox1.Image = bitmap;
image = bitmap ;
}
Bitmap image;
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
//Create a path and add lines.
List<Point> outlinePoints = new List<Point>();
BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] originalBytes = new byte[image.Width * image.Height * 4];
Marshal.Copy(bitmapData.Scan0, originalBytes, 0, originalBytes.Length);
//find non-transparent pixels visible from the top of the image
for (int x = 0; x < bitmapData.Width; x++)
{
for (int y = 0; y < bitmapData.Height; y++)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
outlinePoints.Add(p);
break;
}
}
}
//helper variable for storing position of the last pixel visible from both sides
//or last inserted pixel
int? lastInsertedPosition = null;
//find non-transparent pixels visible from the right side of the image
for (int y = 0; y < bitmapData.Height; y++)
{
for (int x = bitmapData.Width - 1; x >= 0; x--)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
{
if (lastInsertedPosition.HasValue)
{
outlinePoints.Insert(lastInsertedPosition.Value + 1, p);
lastInsertedPosition += 1;
}
else
{
outlinePoints.Add(p);
}
}
else
{
//save last common pixel from visible from more than one sides
lastInsertedPosition = outlinePoints.IndexOf(p);
}
break;
}
}
}
lastInsertedPosition = null;
//find non-transparent pixels visible from the bottom of the image
for (int x = bitmapData.Width - 1; x >= 0; x--)
{
for (int y = bitmapData.Height - 1; y >= 0; y--)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
{
if (lastInsertedPosition.HasValue)
{
outlinePoints.Insert(lastInsertedPosition.Value + 1, p);
lastInsertedPosition += 1;
}
else
{
outlinePoints.Add(p);
}
}
else
{
//save last common pixel from visible from more than one sides
lastInsertedPosition = outlinePoints.IndexOf(p);
}
break;
}
}
}
lastInsertedPosition = null;
//find non-transparent pixels visible from the left side of the image
for (int y = bitmapData.Height - 1; y >= 0; y--)
{
for (int x = 0; x < bitmapData.Width; x++)
{
byte alpha = originalBytes[y * bitmapData.Stride + 4 * x + 3];
if (alpha != 0)
{
Point p = new Point(x, y);
if (!ContainsPoint(outlinePoints, p))
{
if (lastInsertedPosition.HasValue)
{
outlinePoints.Insert(lastInsertedPosition.Value + 1, p);
lastInsertedPosition += 1;
}
else
{
outlinePoints.Add(p);
}
}
else
{
//save last common pixel from visible from more than one sides
lastInsertedPosition = outlinePoints.IndexOf(p);
}
break;
}
}
}
// Added to close the loop
outlinePoints.Add(outlinePoints[0]);
image.UnlockBits(bitmapData);
GraphicsPath myPath = new GraphicsPath();
myPath.AddLines(outlinePoints.ToArray());
// return outlinePoints.ToArray();
PathGradientBrush pthGrBrush = new PathGradientBrush(outlinePoints.ToArray());
//foreach (Point p in outlinePoints.ToArray())
//{
// //Point p1 = p;
//}
Color[] colors = { Color.Red , Color.Green };
pthGrBrush.SurroundColors = colors;
pthGrBrush.CenterColor = Color.Red;
Blend blend = new Blend();
// blend.Factors = new float[] { 1, 0 };
blend.Positions = new float[] { 0, 0.25f };
pthGrBrush.Blend = blend;
// LinearGradientBrush linGrBrush = new LinearGradientBrush(new Point(0, 10),new Point(200, 10),Color.FromArgb(255, 255, 0, 0),Color.FromArgb(255, 0, 0, 255));
e.Graphics.FillPath(pthGrBrush, myPath);
// Pen myPen = new Pen(Color.Black, 6);
// e.Graphics.DrawPath(pthGrBrush, myPath);
}

Drawing points along path spirally

Well, I'm trying to optimize what I did here (Smoothing noises with different amplitudes (Part 2)).
By this reason, I did a new implementation from scratch (https://youtu.be/o7pVEXhh3TI) to draw the path:
private void Start()
{
Polygon pol = File.ReadAllText(PolyPath).Deserialize<Polygon>();
// Create tex object
var list = pol.Vertices.AsEnumerable();
tex = list.CreateTextureObject(pol.Position, offset);
exampleTexture = new Texture2D(tex.Width, tex.Height);
exampleTexture.SetPixels32(new Color32[tex.Width * tex.Height]);
exampleTexture.Apply();
vertices = pol.Vertices.Select(v => (v - pol.Position) + offset).Clone().ToList();
_ss = new List<Segment>(pol.Segments.Select(s => new Segment((s.start + pol.Center - pol.Position) + offset, (s.end + pol.Center - pol.Position) + offset)));
foreach (Segment curSeg in _ss)
for (int i = -effectDistance; i < effectDistance; ++i)
{
Vector2 perp = Vector2.Perpendicular(((Vector2)curSeg.start - (Vector2)curSeg.end)).normalized;
segments.Add((Vector2)curSeg.start + perp * i);
F.DrawLine((Vector2)curSeg.start + perp * i, (Vector2)curSeg.end + perp * i, (x, y) => layers.Add(new Point(x, y)));
}
Debug.Log("Layer Count: " + layers.Count);
drawPath = true;
}
private void OnGUI()
{
if (exampleTexture == null)
return;
GUI.DrawTexture(new Rect((Screen.width - tex.Width) / 2, (Screen.height - tex.Height) / 2, tex.Width, tex.Height), exampleTexture);
if (drawPath)
{
{
Point? cur = layers.Count > 0 ? (Point?)layers.First() : null;
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(170, 0, 0, 255));
exampleTexture.Apply();
layers.Remove(cur.Value);
}
}
{
Point? cur = segments.Count > 0 ? (Point?)segments.First() : null;
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(0, 170, 0, 255));
exampleTexture.Apply();
segments.Remove(cur.Value);
}
}
{
Point? cur = vertices.Count > 0 ? (Point?)vertices.First() : null;
//Debug.Log(cur);
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(255, 128, 0, 255));
exampleTexture.Apply();
vertices.Remove(cur.Value);
}
}
if (vertices.Count == 0 && segments.Count == 0 && layers.Count == 0)
drawPath = false;
}
}
This is what DrawLines actually do:
public static class F
{
public static void DrawLine(Point p1, Point p2, Action<int, int> action)
{
DrawLine(p1.x, p1.y, p2.x, p2.y, action);
}
public static void DrawLine(int x0, int y0, int x1, int y1, Action<int, int> action)
{
int sx = 0,
sy = 0;
int dx = Mathf.Abs(x1 - x0),
dy = Mathf.Abs(y1 - y0);
if (x0 < x1) { sx = 1; } else { sx = -1; }
if (y0 < y1) { sy = 1; } else { sy = -1; }
int err = dx - dy,
e2 = 0;
while (true)
{
action?.Invoke(x0, y0);
if ((x0 == x1) && (y0 == y1))
break;
e2 = 2 * err;
if (e2 > -dy)
{
err = err - dy;
x0 = x0 + sx;
}
if (e2 < dx)
{
err = err + dx;
y0 = y0 + sy;
}
}
}
}
This is an implemenentation of Bresenham algorithm.
This implementation is better because I have lowered iterations from 280k to 6k, but there is an issue as you can see this is innacurate...
The way this works first is getting the perpendicular of each segment on the shape (green pixels) and then drawing lines between the start and the end point of that segment. Segmenents are obtained using Ramer-Douglas-Peucker algorithm.
So I was thinking on draw the "orange" path spirally. I don't know how to explain this, basically, obtaining the same path but, with an scale (Translating/transforming? list of points from its center with an offset/distance) but I think I will have the same innacuracy.
Any guide will be appreciated. What algorithm could I use to draw the path with "layers"?
Following some of the information here, you might be able to use "inward/outward polygon offsetting" (aka "polygon buffering") to get the result you are interested in.
A tool such as Clipper can help.
Once you have a way to outwardly offset your shape, do the following:
First, draw the outer shape (black region below), then offset the inner shape outwards as far as you need it to go, and draw it on top of the outer shape (brown region below) using an appropriate noise/color scheme:
Then, apply a smaller offset, then draw that shape on top using a different noise/colorscheme (orange region below).
Repeat until you have as many gradients as you need:
Finally, draw the inner shape without any offsetting with its noise/color scheme:

Optical Mark Recognition using C#

This is a function which determines if a point is within the boundary of the image and checks if it overlaps with other circle.
If it returns true then i check the threshold of the circle filled black and later save the point which is filled more than 90% into a list of points.
My program works in 2 steps
1) If it forms a circle with no overlaping.
2) If it is 90% filled.
I am facing a error that if i pass a image with only 1 circle it saves 1408 circles.I have no idea what i am doing wrong.
Below is the button click event
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 50; j++)
{
p.X = j;
p.Y = i;
if (isCircle(p))
{
if (checkThreshold(p) > 90)
{
pts.Insert(0, p);
}
}
}
}
Below given are the functions
private bool isCircle(Point p)
{
double count = 0;
Point curP = new Point();
//Point centre = new Point(24, 20);
int a = 0;
boundary.X = 50;
boundary.Y = 50;
for (int x = (p.X - radius); x <= (p.X - radius); x++)
{
for (int y = (p.Y - radius); y <= (p.Y - radius); y++)
{
if ((x < boundary.X) && (y < boundary.Y) && (x + radius < boundary.X) && (y + radius < boundary.Y))
{
curP.X = 0;
curP.Y = 0;
curP.X = x;
curP.Y = y; //stores new point to be sent in curP
while (a < pts.Count)
{
//point , centre, radius
if (checkOverlap(curP, pts[a], radius) == false) //send point to check if it overlaps or not
{
// MessageBox.Show("yellow");
count = 1;
break;
}
else
{
a++;
}
}
}
if (count == 1)
break;
}
if (count == 1)
break;
}
if (count == 1)
return true;
else return false;
}
Below given is the checkOverlap function
private bool checkOverlap(Point p, Point c, int radii)
{
Point listPoint;
listPoint = p;
//the following if condition checks if the point resides in the list or not
if ((((c.X - radii) < listPoint.X) && (listPoint.X > (c.X - radii))) && (((c.Y - radii) < listPoint.Y) && (listPoint.Y > (c.Y - radii))))
{
if ((((p.X - c.X) * (p.X - c.X)) - ((p.Y - c.Y) * (p.Y - c.Y))) < (radius * radius))
{
return false;
}
return true;
}
else
return true;
}
Not sure if this is THE issue, but your count variable should be an int due to the way you are testing for equality.

Looping through pixels excluding an area

I have the following code for looping pixels in an image:
Bitmap myBitmap = new Bitmap(pictureBox1.Image);
for (x = 0; x < pictureBox1.Image.Width; x += 1)
{
for (y = 0; y < pictureBox1.Image.Height; y += 1)
{
Color pixelColor = myBitmap.GetPixel(x, y);
aws = pixelColor.GetBrightness();
}
}
The above works. What if i want to exclude an area of the image. The area is an area selected by the user,for example:
from x=200&y=30 to x=250&y=30
from x=200&y=35 to x=250&y=35
from x=200&y=40 to x=250&y=40
from x=200&y=45 to x=250&y=45
How i loop all pixels except the one i wrote above?
Thank you!
I think you can do something like the following
Rectangle rectToExclude = new Rectangle(200, 30, 250, 30);//rectangle to be excluded
Bitmap myBitmap = new Bitmap(pictureBox1.Image);
for (x = 0; x < pictureBox1.Image.Width; x += 1)
{
for (y = 0; y < pictureBox1.Image.Height; y += 1)
{
if (rectToExclude.Contains(x, y)) continue; // if in the exclude rect then continue without doing any effects
Color pixelColor = myBitmap.GetPixel(x, y);
aws = pixelColor.GetBrightness();
}
}
Maybe write a function to test x and y like this:
public static bool IsInArea(int testx, int testy, int x1, int y1, int x2, int y2)
{
if (testx > x1 && testx < x2 && testy > y1 && testy < y2)
{
return true;
}
else
{
return false;
}
}
and then use it like this:
BitmapData bmd = myBitmap.LockBits(new Rectangle(0, 0, myBitmap.Width, myBitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite,
myBitmap.PixelFormat);
int PixelSize = 4;
unsafe
{
for (int y = 0; y < bmd.Height; y++)
{
byte* row = (byte*)bmd.Scan0 + (y * bmd.Stride);
for (int x = 0; x < bmd.Width; x++)
{
if (!IsInArea(x, y, 200, 30, 250, 30))
{
int b = row[x * PixelSize]; //Blue
int g = row[x * PixelSize + 1]; //Green
int r = row[x * PixelSize + 2]; //Red
int a = row[x * PixelSize + 3]; //Alpha
Color c = Color.FromArgb(a, r, g, b);
float brightness = c.GetBrightness();
}
}
}
}
myBitmap.UnlockBits(bmd);

Calculate pixels within a polygon

In an assignment for school do we need to do some image recognizing, where we have to find a path for a robot.
So far have we been able to find all the polygons in the image, but now we need to generate a pixel map, that be used for an astar algorithm later. We have found a way to do this, show below, but the problem is that is very slow, as we go though each pixel and test if it is inside the polygon. So my question is, are there a way that we can generate this pixel map faster?
We have a list of coordinates for the polygon
private List<IntPoint> hull;
The function "getMap" is called to get the pixel map
public Point[] getMap()
{
List<Point> points = new List<Point>();
lock (hull)
{
Rectangle rect = getRectangle();
for (int x = rect.X; x <= rect.X + rect.Width; x++)
{
for (int y = rect.Y; y <= rect.Y + rect.Height; y++)
{
if (inPoly(x, y))
points.Add(new Point(x, y));
}
}
}
return points.ToArray();
}
Get Rectangle is used to limit the search, se we don't have to go thoug the whole image
public Rectangle getRectangle()
{
int x = -1, y = -1, width = -1, height = -1;
foreach (IntPoint item in hull)
{
if (item.X < x || x == -1)
x = item.X;
if (item.Y < y || y == -1)
y = item.Y;
if (item.X > width || width == -1)
width = item.X;
if (item.Y > height || height == -1)
height = item.Y;
}
return new Rectangle(x, y, width-x, height-y);
}
And atlast this is how we check to see if a pixel is inside the polygon
public bool inPoly(int x, int y)
{
int i, j = hull.Count - 1;
bool oddNodes = false;
for (i = 0; i < hull.Count; i++)
{
if (hull[i].Y < y && hull[j].Y >= y
|| hull[j].Y < y && hull[i].Y >= y)
{
try
{
if (hull[i].X + (y - hull[i].X) / (hull[j].X - hull[i].X) * (hull[j].X - hull[i].X) < x)
{
oddNodes = !oddNodes;
}
}
catch (DivideByZeroException e)
{
if (0 < x)
{
oddNodes = !oddNodes;
}
}
}
j = i;
}
return oddNodes;
}
There are some interesting discussion here on polygon hit tests, but it sounds to me as if you might be better off with a polygon fill.
You may want to look for a Plygon Triangulation algorithm.
Also, note that catching an exception is far more time-consuming that checking the right condition. So i suggest you to convert your existing code in:
public bool inPoly(int x, int y)
{
int i, j = hull.Count - 1;
var oddNodes = false;
for (i = 0; i < hull.Count; i++)
{
if (hull[i].Y < y && hull[j].Y >= y
|| hull[j].Y < y && hull[i].Y >= y)
{
var delta = (hull[j].X - hull[i].X);
if (delta == 0)
{
if (0 < x) oddNodes = !oddNodes;
}
else if (hull[i].X + (y - hull[i].X) / delta * delta < x)
{
oddNodes = !oddNodes;
}
}
j = i;
}
return oddNodes;
}

Categories