问题描述:
最近需要实现当重复画图时候,色彩根据重叠次数显示指定颜色。来达到模拟现实压路机实况。
使用winform实现。
解决思路:
1.首先在winform上实现,最简单的配置就是利用Graphics 进行画图。
2.将所有的图画都相当于是坐标点的处理-Point。
然后线就等于两个点的连线组成直线。图形就相当于线给于一定的宽度。
3.这样一条笔直的直线,可以拆分成许多数量的短线段来处理。短线段又可以当做矩形处理,即时是斜着的,或者不规则的矩形,我们可以进行两段处理,第一段利用矩形的特征,去过滤掉不重叠的线段,减少无效处理,筛选完成后,在进行精确处理,利用Region的Intersect获得相交区域。
解决方案:
1.前置准备工作。
准备颜色表。
List<Color> colorList = new List<Color>();
colorList.Add(Color.Red);
colorList.Add(Color.Green);
colorList.Add(Color.Blue);
colorList.Add(Color.Black);
colorList.Add(Color.Orange);
colorList.Add(Color.Yellow);
colorList.Add(Color.White);
画线的代码
Pen pen = new Pen(color, 40);
pe.Graphics.DrawLine(pen, lastPoint,_point);
//pen是画笔,定义颜色和线宽。lastPoint和_point是两个点,不用在意起名。就是对应的线的起点和终点。
初始化的,每个0.5秒将点变化,然后将新的点存放进列表list里面,自己有数据来源的,可以把这部分替换成自己的变化数据源。
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Interval = 500; // 1000毫秒(1秒)
timer.Tick += Timer_Tick;
timer.Start();
private void Timer_Tick(object sender, EventArgs e)
{
if (_isRunning)
{
Point startPoint = new Point(_point.X, _point.Y);
_point.X += 10; // 每秒更新点的X坐标
_point.Y += 10; // 每秒更新点的Y坐标
//lastPoint = _point;
segments.Add(new LineSegment(startPoint, new Point(_point.X, _point.Y), 40));
// 触发重绘事件
this.Invalidate();
}
}
添加图片作为模型图标的代码。png图片旋转角度,我这里设置的是45度,图片的宽高尺寸设的是110和110.
pe.Graphics.DrawImage(image1, _point.X - 20, _point.Y - 20, 110, 110);
Image image = Image.FromFile(path);
image1 = RotateImage(image, 45);
//图标旋转的方法
public static Image RotateImage(Image img, float angle)
{
// 计算旋转后的长方形内接矩形大小
double sin = Math.Abs(Math.Sin(angle * Math.PI / 180.0));
double cos = Math.Abs(Math.Cos(angle * Math.PI / 180.0));
int newWidth = (int)(img.Width * cos + img.Height * sin);
int newHeight = (int)(img.Width * sin + img.Height * cos);
Bitmap b = new Bitmap(newWidth, newHeight);
Graphics g = Graphics.FromImage(b);
g.TranslateTransform((float)b.Width / 2, (float)b.Height / 2);
g.RotateTransform(angle);
g.TranslateTransform(-(float)img.Width / 2, -(float)img.Height / 2);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(img, new Point(0, 0));
g.Dispose();
return b;
}
2.我们所有的算法都在form的OnPaint里面写,这样可以不断的刷新页面,同步显示信息。
直接复制进去就行,不用和页面做操作,override 就是重写。
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
//算法在这里添加
}
3.主要算法
为了便利,创建LineSegment类进行管理,嫌麻烦的也可以不要,这个LineSegment主要是存放起点,终点,和线宽,以及判断方法,放一起好管理,也可以拆开,看个人喜好。
其中的GetBounds方法就是用来判断两个线是否重叠,这个方法适用不同角度的线,当然也不够精确,会把一定范围内的线也算进来的,但是这步只是筛选,将数据大幅度减少。
后面的GetOverlapRegion方法,才是精确处理,重叠区域,如果不重叠返回null就可以。
public class LineSegment
{
public Point StartPoint { get; set; }
public Point EndPoint { get; set; }
public int Width { get; set; }
public LineSegment(Point startPoint, Point endPoint, int width)
{
StartPoint = startPoint;
EndPoint = endPoint;
Width = width;
}
public RectangleF GetBounds()
{
//Line line1 = new Line();
//line1.Width = Width;
//line1.X1 = StartPoint.X;
//line1.Y1 = StartPoint.Y;
//line1.X2 = EndPoint.X;
//line1.Y2 = EndPoint.Y;
float left = Math.Min(StartPoint.X, EndPoint.X) - Width / 2;
float top = Math.Min(StartPoint.Y, EndPoint.Y) - Width / 2;
float width = Math.Abs(StartPoint.X - EndPoint.X) + Width;
float height = Math.Abs(StartPoint.Y - EndPoint.Y) + Width;
return new RectangleF(left, top, width, height);
}
public Region GetOverlapRegion(LineSegment otherSegment)
{
RectangleF bounds = GetBounds();
RectangleF otherBounds = otherSegment.GetBounds();
if (!bounds.IntersectsWith(otherBounds)) return null;
GraphicsPath path1 = new GraphicsPath();
path1.AddLine(StartPoint, EndPoint);
path1.Widen(new Pen(Color.Black, Width));
GraphicsPath path2 = new GraphicsPath();
path2.AddLine(otherSegment.StartPoint, otherSegment.EndPoint);
path2.Widen(new Pen(Color.Black, otherSegment.Width));
Region overlapRegion = new Region(path1);
overlapRegion.Intersect(path2);
if (overlapRegion!=null &&!overlapRegion.IsEmpty(Graphics.FromImage(new Bitmap(1, 1))))
{
return overlapRegion;
}
else
{
return null;
}
}
}
使用时,将存放有线的列表,进行两两比较,获得重叠区域,按照字典存放进去,为什么用字典,是利用了字典键的唯一性,后面进行颜色处理比较便利。
Dictionary<int,List<Region>> overlapRegions = new Dictionary<int, List<Region>>();
for (int i = 0; i < segments.Count; i++)
{
for (int j = i + 1; j < segments.Count; j++)
{
LineSegment segment1 = segments[i];
LineSegment segment2 = segments[j];
Region overlapRegion = segment1.GetOverlapRegion(segment2);
if (overlapRegion != null)
{
if (overlapRegions.ContainsKey(i))
{
overlapRegions[i].Add(overlapRegion);
}
else
{
List<Region> regions = new List<Region>();
regions.Add(overlapRegion);
overlapRegions.Add(i, regions);
}
}
}
}
这样得到了,只要重叠了就会存进去,如果同一节线段和多个线段重叠,那么在对应键的下,vale值的列表数量就会变多,可以帮助我们划分颜色层数。
//
下面先将字典按照里面列表数量进行排序,其实不处理也行,习惯了,处理后方面后面数据容错。
然后获得最大重叠层数。
然后从第1层颜色开始遍历重叠区域,然后用Graphics.FillRegion填充颜色。
var sortedDictionary = overlapRegions.OrderBy(pair => pair.Value.Count).ToDictionary(pair => pair.Key, pair => pair.Value);
int maxCount = 0;
if (overlapRegions.Count > 0)
{
maxCount = overlapRegions.Values.Max(list => list.Count);
}
for(int i = 0; i < maxCount; i++)
{
foreach (var sub in sortedDictionary)
{
List<Region> regions = sub.Value;
if (i < regions.Count)
{
int x = i + 1;
if (x < colorList.Count)
{
}
else
{
x = colorList.Count - 1;
}
if (x < 1)
{
x = 1;
}
pe.Graphics.FillRegion(new SolidBrush(colorList[x]), regions[i]);
}
}
}