如何只画一行一行

时间:2014-10-26 12:26:29

标签: c# .net winforms graphics gdi+

我使用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

1 个答案:

答案 0 :(得分: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()方法添加矩形(或个别行,如果需要),然后最后枚举所有生成的行。

我希望这可以很好地执行数千行左右。在我的测试中,我每次在绘制窗口时都会从头开始重新创建集合。

取决于PC,即使在更高的幅度下它也可能表现得足够好,但我没有尝试进行任何特定的优化,而是选择易于理解的代码。在您处理极大数量矩形的情况下,您可能希望保留持久实例以在生成行时收集行/矩形。那么你不必每次油漆事件都要重新生成它。那可能需要也可能不需要在类中添加功能以支持删除行。