2014-10-26 3 views
1

Я использую GDI+, чтобы визуализировать схему определенного пользовательского здания. В нем нет сложных объектов - все они могут быть представлены прямоугольниками. Я делаю это, но имею одну проблему: много прямоугольников перекрываются, например. затем номера смежные. Так что некоторые линии рисовали много раз! Это выглядит плохо (толстая линия) и снижает производительность приложения (дополнительная работа).Как нарисовать одну строку только один раз

Есть ли способ рисовать каждую строку только один раз на экране?

Мой код (упрощенно) выглядит следующим образом:

private void Visualizator_Paint(object sender, PaintEventArgs e) 
{ 
     if (m_building == null) return; 

     var g = e.Graphics; 

     // Smooth graphics output and scale 
     g.SmoothingMode = SmoothingMode.HighQuality; 
     ScaleGraphics(g); 

     ... 

     foreach(var room in m_rooms) 
     { 
      RectangleF extent = room.Extent; 
      g.DrawRectangle(brownPen, extent.X, extent.Y, extent.Width, extent.Height); 
     } 

     ... 
    } 

    void ScaleGraphics(Graphics g) 
    { 
     // Set margins inside the control client area in pixels 
     var margin = new Margins(16, 16, 16, 16); 

     // Set the domain of (x,y) values 
     var range = m_building.Extents; 

     // Make it smaller by 5% 
     range.Inflate(0.05f * range.Width, 0.05f * range.Height); 

     // Scale graphics 
     ScaleGraphics(g, Visualizator, range, margin); 
    } 

    void ScaleGraphics(Graphics g, Control control, RectangleF domain, Margins margin) 
    { 
     // Find the drawable area in pixels (control-margins) 
     int W = control.Width - margin.Left - margin.Right; 
     int H = control.Height - margin.Bottom - margin.Top; 

     // Ensure drawable area is at least 1 pixel wide 
     W = Math.Max(1, W); 
     H = Math.Max(1, H); 

     // Find the origin (0,0) in pixels 
     float OX = margin.Left - W * (domain.Left/domain.Width); 
     float OY = margin.Top + H * (1 + domain.Top/domain.Height); 

     // Find the scale to fit the control 
     float SX = W/domain.Width; 
     float SY = H/domain.Height; 

     // Transform the Graphics scene 
     if (m_panPoint.IsEmpty) 
      m_panPoint = new PointF(OX, OY); 

     g.TranslateTransform(m_panPoint.X, m_panPoint.Y, MatrixOrder.Append); 
     g.ScaleTransform(SX * m_scale, -SY * m_scale); 
    } 

Скриншот дефекта: antialias consequences

+0

Я не понимаю, в чем ваша проблема. но вы можете очистить перед рисованием рекордов g.Clear (Color.) Плюс лучше использовать двойную буферизацию – qwr

+0

@qwr Многие из прямоугольников (комнат) смежны, поэтому некоторые (много) линии рисуют более одного раза. Я хочу нарисовать такие «общие» строки только раз. – eraxillan

+0

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

ответ

1

Я не смог воспроизвести размывания/эффект смазывание, описанный в этом вопросе. Тем не менее, основной запрос, чтобы быть в состоянии избежать чрезмерного рисования линий, кажется достаточно понятным и не слишком сложным для решения. Поэтому я предлагаю этот класс, который может выполнять эту работу:

/// <summary> 
/// Consolidates horizontal and vertical lines. 
/// </summary> 
class LineConsolidator : IEnumerable<LineConsolidator.Line> 
{ 
    /// <summary> 
    /// A pair of points defining a line 
    /// </summary> 
    public struct Line 
    { 
     public Point Start { get; private set; } 
     public Point End { get; private set; } 

     public Line(Point start, Point end) 
      : this() 
     { 
      Start = start; 
      End = end; 
     } 
    } 

    private struct Segment 
    { 
     public int Start { get; private set; } 
     public int End { get; private set; } 

     public Segment(int start, int end) 
      : this() 
     { 
      if (end < start) 
      { 
       throw new ArgumentException("start must be less than or equal to end"); 
      } 

      Start = start; 
      End = end; 
     } 

     public Segment Union(Segment other) 
     { 
      if (End < other.Start || other.End < Start) 
      { 
       throw new ArgumentException("Only overlapping segments may be consolidated"); 
      } 

      return new Segment(
        Math.Min(Start, other.Start), 
        Math.Max(End, other.End)); 
     } 

     public Segment? Intersect(Segment other) 
     { 
      int start = Math.Max(Start, other.Start), 
       end = Math.Min(End, other.End); 

      if (end < start) 
      { 
       return null; 
      } 

      return new Segment(start, end); 
     } 
    } 

    private Dictionary<int, List<Segment>> _horizontalLines = new Dictionary<int, List<Segment>>(); 
    private Dictionary<int, List<Segment>> _verticalLines = new Dictionary<int, List<Segment>>(); 

    /// <summary> 
    /// Add horizontal line 
    /// </summary> 
    /// <param name="y">The Y coordinate of the line to add</param> 
    /// <param name="start">The first X coordinate of the line to add (must not be larger than <paramref name="end"/></param> 
    /// <param name="end">The second X coordinate of the line to add (must not be smaller than <paramref name="start"/></param> 
    /// <remarks> 
    /// This method submits a new horizontal line to the collection. It is merged with any other 
    /// horizontal lines with exactly the same Y coordinate that it overlaps. 
    /// </remarks> 
    public void AddHorizontal(int y, int start, int end) 
    { 
     _AddLine(y, new Segment(start, end), _horizontalLines); 
    } 

    /// <summary> 
    /// Add vertical line 
    /// </summary> 
    /// <param name="y">The X coordinate of the line to add</param> 
    /// <param name="start">The first Y coordinate of the line to add (must not be larger than <paramref name="end"/></param> 
    /// <param name="end">The second Y coordinate of the line to add (must not be smaller than <paramref name="start"/></param> 
    /// <remarks> 
    /// This method submits a new vertical line to the collection. It is merged with any other 
    /// vertical lines with exactly the same X coordinate that it overlaps. 
    /// </remarks> 
    public void AddVertical(int x, int start, int end) 
    { 
     _AddLine(x, new Segment(start, end), _verticalLines); 
    } 

    /// <summary> 
    /// Add all four sides of a rectangle as individual lines 
    /// </summary> 
    /// <param name="rect">The rectangle containing the lines to add</param> 
    public void AddRectangle(Rectangle rect) 
    { 
     AddHorizontal(rect.Top, rect.Left, rect.Right); 
     AddHorizontal(rect.Bottom, rect.Left, rect.Right); 
     AddVertical(rect.Left, rect.Top, rect.Bottom); 
     AddVertical(rect.Right, rect.Top, rect.Bottom); 
    } 

    /// <summary> 
    /// Gets all of the horizontal lines in the collection 
    /// </summary> 
    public IEnumerable<Line> HorizontalLines 
    { 
     get 
     { 
      foreach (var kvp in _horizontalLines) 
      { 
       foreach (var segment in kvp.Value) 
       { 
        yield return new Line(new Point(segment.Start, kvp.Key), new Point(segment.End, kvp.Key)); 
       } 
      } 
     } 
    } 

    /// <summary> 
    /// Gets all of the vertical lines in the collection 
    /// </summary> 
    public IEnumerable<Line> VerticalLines 
    { 
     get 
     { 
      foreach (var kvp in _verticalLines) 
      { 
       foreach (var segment in kvp.Value) 
       { 
        yield return new Line(new Point(kvp.Key, segment.Start), new Point(kvp.Key, segment.End)); 
       } 
      } 
     } 
    } 

    private static void _AddLine(int lineKey, Segment newSegment, Dictionary<int, List<Segment>> segmentKeyToSegments) 
    { 
     // Get the list of segments for the given key (X for vertical lines, Y for horizontal lines) 
     List<Segment> segments; 

     if (!segmentKeyToSegments.TryGetValue(lineKey, out segments)) 
     { 
      segments = new List<Segment>(); 
      segmentKeyToSegments[lineKey] = segments; 
     } 

     int isegmentInsert = 0, isegmentMergeFirst = -1, ilineSegmentLast = -1; 

     // Find all existing segments that should be merged with the new one 
     while (isegmentInsert < segments.Count && segments[isegmentInsert].Start <= newSegment.End) 
     { 
      Segment? intersectedSegment = newSegment.Intersect(segments[isegmentInsert]); 

      if (intersectedSegment != null) 
      { 
       // If they overlap, merge them together, keeping track of all the existing 
       // segments which were merged 
       newSegment = newSegment.Union(segments[isegmentInsert]); 

       if (isegmentMergeFirst == -1) 
       { 
        isegmentMergeFirst = isegmentInsert; 
       } 

       ilineSegmentLast = isegmentInsert; 
      } 

      isegmentInsert++; 
     } 

     if (isegmentMergeFirst == -1) 
     { 
      // If there was no merge, just insert the new segment 
      segments.Insert(isegmentInsert, newSegment); 
     } 
     else 
     { 
      // If more than one segment was merged, remove all but one 
      if (ilineSegmentLast > isegmentMergeFirst) 
      { 
       segments.RemoveRange(isegmentMergeFirst + 1, ilineSegmentLast - isegmentMergeFirst); 
      } 

      // Copy the new, merged segment back to the first original segment's slot 
      segments[isegmentMergeFirst] = newSegment; 
     } 
    } 

    public IEnumerator<LineConsolidator.Line> GetEnumerator() 
    { 
     return HorizontalLines.Concat(VerticalLines).GetEnumerator(); 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

Обратите внимание, что это основано на целых координатах. Это немного сложнее применить эту логику к координатам с плавающей запятой, если на самом деле беспокоит необходимость округлять ошибку. Но если координаты с плавающей запятой гарантированы всегда исходящими из одного и того же источника, когда они перекрываются, то они будут соответствовать условию равенства, которое требуется для реализации этой реализации, и вы можете просто изменить типы на плавающую точку.

Я включил свойства, чтобы извлекать только горизонтальные или вертикальные линии, чтобы я мог рисовать их по-другому друг от друга (разные конечные конечные точки), чтобы проверить, работает ли алгоритм. Обычно я думаю, что вы просто перечислили всю коллекцию при рисовании.

Вы используете его, сначала создавая пустой экземпляр коллекции, затем добавляя ваши прямоугольники (или отдельные строки, если необходимо) с помощью метода AddRectangle(), а затем, наконец, перечисляя все результирующие строки.

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

Он может работать достаточно хорошо даже при более высоких значениях в зависимости от ПК, но я не пытался выполнять какие-либо конкретные оптимизации, вместо этого выбирая простой для понимания код. В ситуации, когда вы имеете дело с чрезвычайно большим количеством прямоугольников, вы можете захотеть сохранить постоянный экземпляр для сбора строк/прямоугольников по мере их создания. Тогда вам не нужно регенерировать его каждое событие рисования. Это может или не может потребовать добавления функций в класс для поддержки удаления строк.

+0

О, спасибо за огромное количество работы! Я попробую ваш код (и покажу скриншот проблемы) по выходным. Им повезло 4 дня: D P.S. Я могу добавить ссылку на мой репозиторий GitGub, весь мой код с открытым исходным кодом – eraxillan

+0

Я добавил скриншот проблемы в сообщение OP. – eraxillan

+0

Мои данные содержат прямоугольники с плавающей запятой. Итак, я должен реализовать «сложную» логику? :) – eraxillan

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