Creating GraphicsPath from a Translucent Bitmap

I want to create a GraphicsPath and a list of points to form the outline of the opaque region of the bitmap. If necessary, I can guarantee that each image has only one continuous collection of opaque pixels. So, for example, I should be able to record points clockwise or counterclockwise along the edge of the pixels and perform a complete closed loop.

The speed of this algorithm is not important. However, the effectiveness of the resulting points does not matter if I can skip some points to reduce in the smaller and less complex GraphicsPath.

I will talk about my current code below, which works great with most images. However, some images that are more complex end up in paths that seem to be connecting in the wrong order. I think I know why this is happening, but I cannot 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 dots in a way:

 GraphicsPath outlinePath = new GraphicsPath(); outlinePath.AddLines(_outlinePoints); 

Here is an example showing what I want. The red outline should be an array of points that can be drawn in GraphicsPath to perform hit detection, draw a contour pen and fill it with a brush.

Example of an Outline

+6
source share
3 answers

Like both of you, you just need to find the first opaque point, and then move around the non-transparent pixels with a transparent neighbor.

Additionally, you will need to save the point that you have already viewed, and how often you visited them, or you end up in the same cases in the invinity loop. If a point does not have a neighbor that has already been visited, you must return each point in a respected direction until an invisible point appears again.

What is it.


// CODE SHOOTING - MAIL WAS NEEDED


EDIT 1

Modified Code:


// CODE SHOOTING - MAIL WAS NEEDED


EDIT 2

Now all regions are returned:


// CODE SHOOTING - MAIL WAS NEEDED


EDIT 3

Changes:

  • Point.EMPTY has been replaced by a dot (-1, -1) or an opaque pixel in the upper left corner causes invinityloop
  • Check border on 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(pY * bmp.Width + pX, 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[pY * size.Width + pX]) { duplicatedpos = i - 1; break; } duplicatecheck[pY * size.Width + pX] = 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]; } } } 
+5
source

I modified GetOutlinePoints by adding a helper variable, which checks the position at which new points should be added.

The idea of โ€‹โ€‹the contour detection algorithm in your code is something like searching for an image standing on each of its edges and recording all the opaque pixels that are visible. This is normal, however, you always added pixels to the end of the collection, which caused problems. I added a variable that remembers the position of the last pixel visible from the previous edge and the current one, and uses it to determine the index at which the new pixel should be added. I assume that it should work as long as the contour is continuous, but I suppose this is in your case.

I tested it on multiple 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 will not work correctly for images in which the contour parts are not "visible" from any of the edges. See comments on proposed solutions. I will try to post a code snippet later.

Update II

I prepared another algorithm as described in my comments. It just walks around the object and maintains the outline.

First, he finds the first pixel of the path using the method from the previous algorithm. Then it scans adjacent pixels clockwise, finds the first transparent one, and then continues to view, but searches for an opaque one. The first opaque pixel is found next in the path. The cycle continues until it bypasses the entire object and returns to the original 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 works. An IF object does NOT end at the edge of the image (there should be a transparent space between the object and each of the edges).

This can be easily done if you simply make the image one line larger on each side before passing it to the GetOutlinePointsNEW method.

+3
source

The script under the poster was one of those that I just encountered. After applying the GetOutlinePointsNEW method described above, I ran into an indexing problem when an opaque pixel is on the edge of the image, due to which the scan does not fall within the borders of the image. The following is an update to managed code that treats pixels outside borders as opaque when scanning.

  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; } 
+1
source

Source: https://habr.com/ru/post/1402007/


All Articles