2012-03-17 4 views
4

Я хочу создать GraphicsPath и список точек для формирования контура непрозрачной области растрового изображения. При необходимости я могу гарантировать, что каждое изображение имеет только одну сплошную коллекцию непрозрачных пикселей. Так, например, я должен иметь возможность записывать точки по часовой стрелке или против часовой стрелки вдоль края пикселей и выполнять полный замкнутый цикл.Создание 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

+0

Попробуйте прибегая к помощи Potrace. –

+0

Вчера я добавил обновленный код. Вы проверили это? Это намного короче, чем другие версии, и, похоже, работает так, как вы этого хотели. –

ответ

5

Как и вы, вы должны просто найти первую непрозрачную точку, а затем двигаться вдоль прозрачных пикселей с прозрачным соседом.

Дополнительно вы должны будете сохранить точку, которую вы уже просматривали, и как часто вы их посещали, или вы закончите в тех же случаях в цикле invinity. Если точка не имеет соседа, который уже был посещен, вы должны возвращать каждую точку в почитаемом направлении до тех пор, пока снова не появится невидимая точка.

Все.


// КОД УДАЛЕНЫ - ПОСТ БЫЛ LONG


EDIT 1

Модифицированный код:


// КОД УДАЛЕНЫ - ПОСТ БЫЛ LONG


EDIT 2

Теперь все регионы возвращаются:


// КОД УДАЛЕНЫ - ПОСТ БЫЛ LONG


EDIT 3

Ch ANGES:

  • Point.EMPTY был заменен Point (-1, -1), или непрозрачный пиксель в верхнем левом углу вызывает invinityloop
  • Проверить borderpoint на границе изображения

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


} 
+0

Хм было несколько изображений, с которыми этот метод не работал, но другой метод. Попытка выяснить, почему. –

+0

извините, было 01:00 и я устал :) см. Мой отредактированный код. у вас все равно будет небольшая уродливая граница в узких местах. Чтобы заполнить их, вы должны идти по всем возможным направлениям от точки и продолжать свой путь с кратчайшей линией между узким пространством. – Wowa

+0

теперь я немного изменил процедуру. Теперь он создает все возможные регионы и объединяет соседние регионы с одним. если вы просто хотите, чтобы все outlineregions просто добавляли все регионы в графический путь и проверяли, пересекаются ли пути. – Wowa

2

Я изменил 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(); 
    } 

Update: Этот алгоритм не будет работать корректно для изображений, которые имеют части контурные не «видимые» из любого краев. См. Комментарии к предлагаемым решениям. Я попытаюсь опубликовать фрагмент кода позже.

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

Одна вещь, чтобы помнить о том, что она будет работать, если объект не заканчивается на краю изображения (там должен быть прозрачным пространство между объектом и каждое из ребер).

Это легко сделать, если вы просто сделаете изображение на одной линии больше с каждой стороны, прежде чем передавать его методу GetOutlinePointsNEW.

+0

Да, похоже, это отлично работает со всеми моими 50 или около того. –

+0

Вы ударяете гвоздь на голову этим. Я знал, что мне нужно вставить эти странные точки, но я понятия не имел, как рассчитать, где. Я должен подождать 22 часа, чтобы наградить свою награду. –

+0

Я рад, что это помогло :). В дальнейшем я буду более подробно разбираться в этом коде, потому что я думаю, что не уверен, правильно ли он работает в некоторых особых случаях. Я исследую это позже.Кроме того, дайте мне знать, если вы обнаружите ошибку в этом коде. –

Смежные вопросы