从半透明位图创建GraphicsPath

时间:2012-03-17 18:00:03

标签: c# bitmap gdi+ graphicspath

我想创建一个GraphicsPath和一个Points列表,以形成位图非透明区域的轮廓。如果需要,我可以保证每个图像只有一个非透明像素的固体集合。因此,例如,我应该能够沿像素边缘顺时针或逆时针记录点,并执行完全闭环。

此算法的速度并不重要。但是,如果我可以在较小且不太复杂的GraphicsPath中跳过某些点来减少,那么结果点的效率是非常重要的。

我将列出下面的当前代码,该代码与大多数图像完美匹配。但是,一些更复杂的图像最终会出现以错误顺序连接的路径。我想我知道为什么会这样,但我无法提出解决方案。

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;
}

当我把这些点转变成一条道路时:

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

这是一个展示我想要的例子。红色轮廓应该是一个点阵列,可以制作成一个GraphicsPath,以便执行命中检测,绘制轮廓笔,并用画笔填充它。

Example of an Outline

3 个答案:

答案 0 :(得分:5)

像你们两个人一样,你必须找到第一个非透明点,然后沿着透明邻居的非透明像素移动。

另外,您必须保存已经访问过的点以及您访问它们的频率,或者您将在相同的情况下以无限循环结束。如果该点没有已经访问过的邻居,则必须以受尊方向返回每个点,直到再次可用的未访问点。

就是这样。


//删除代码 - 发布时间长


编辑1

修改后的代码:


//删除代码 - 发布时间长


编辑2

现在返回所有地区:


//删除代码 - 发布时间长


编辑3

的变化:

  • Point.EMPTY由Point(-1,-1)或非透明像素替换 在左上角导致invinityloop
  • 检查图像边框处的边界点

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];
        }
    }


}

答案 1 :(得分:3)

我通过添加辅助变量来修改GetOutlinePoints,而不是验证应添加新点的位置。

代码中的轮廓检测算法的想法就像在站立每个边缘并查看所有可见的非透明像素时查看图像。没关系,你总是在集合的末尾添加像素,这会导致问题。我添加了一个变量,它记住了从前一个边缘和当前边缘可见的最后一个像素的位置,并使用它来确定应该添加新像素的索引。我认为只要轮廓是连续的,它应该可以工作,但我想这是在你的情况下。

我在几张图片上测试了它,它似乎正常工作:

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();
    }

<强>更新 对于具有任何边缘不“可见”的轮廓部分的图像,此算法将无法正常工作。请参阅建议解决方案的评论我稍后会尝试发布一个代码段。

更新II

我准备了另一种算法,如我的评论中所述。它只是在对象周围爬行并保存轮廓。

首先,它使用前一算法中的方法找到轮廓的第一个像素。然后,它以顺时针顺序查看相邻像素,找到第一个透明然后继续浏览,但寻找不透明的像素。找到的第一个非透明像素是轮廓中的下一个。循环继续,直到它绕过整个对象并返回到起始像素。

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();
    }

要记住的一件事是它确实有效IF如果对象不在图像的边缘结束(在对象和每个边之间必须有一个透明的空间)。

如果您在将图像传递到GetOutlinePointsNEW方法之前将图像的每一边放大一行,则可以轻松完成此操作。

答案 2 :(得分:1)

张贴者的场景是我刚遇到的场景。在应用上面的GetOutlinePointsNEW方法之后,当非透明像素位于图像边缘时,导致爬网超出图像范围时,我遇到了索引问题。下面是一个托管代码更新,在执行爬网时将超出范围的像素视为非透明像素。

    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;
    }