我使用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 );
}
缺陷的屏幕截图:
答案 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,即使在更高的幅度下它也可能表现得足够好,但我没有尝试进行任何特定的优化,而是选择易于理解的代码。在您处理极大数量矩形的情况下,您可能希望保留持久实例以在生成行时收集行/矩形。那么你不必每次油漆事件都要重新生成它。那可能需要也可能不需要在类中添加功能以支持删除行。