玩转你画我猜(一):程序实现自动绘图
说明
本文发布较早,查看 GitHub 项目了解最新动态。(2024 年 3 月 注)
准备
IDE:Visual Studio
Language:VB.NET / C#
GitHub:AutoPaint.NET
通过编程实现自动画画,以后玩你画我猜再也不用担心被吐槽啦ヾ(゚∀゚ゞ)
第一节 导入图片
程序画画,首先得导入一张图片作为模板。
图像格式
我们选择使用位图,它是由像素数据定义的图像格式
WinForm 下使用封装了 GDI+ 位图的 System.Drawing.Bitmap 对象
UWP 下可以使用 CanvasBitmap 对象(需要安装 Win2D 的 Nuget 包)
导入本地文件
直接导入本地图像资源
详情见 System.Drawing.Bitmap 构造函数
使用屏幕截图
先用搜索引擎搜图,然后直接截屏
System.Drawing.Graphics 对象提供从屏幕到 Graphics 的位块传输
动态合成图像
比如合成文本或者图案到指定的图像上
System.Drawing.Graphics 对象提供一系列的 GDI+ 绘图命令
''' <summary> ''' 返回指定矩形区域的屏幕图像 ''' </summary> ''' <param name="rect">指定的矩形区域</param> ''' <returns></returns> Public Function GetScreenImage(ByVal rect As Rectangle) As Bitmap Dim resultBmp As New Bitmap(rect.Width, rect.Height) Using pg As Graphics = Graphics.FromImage(resultBmp) pg.CopyFromScreen(rect.X, rect.Y, 0, 0, New Size(rect.Width, rect.Height)) End Using Return resultBmp End Function
''' <summary> ''' 返回指定文字生成的位图 ''' </summary> ''' <param name="text">文本</param> ''' <param name="font">字体</param> ''' <param name="width">位图宽度</param> ''' <param name="height">位图高度</param> ''' <returns></returns> Public Function GetTextImage(ByVal text As String, ByVal font As Font, ByVal width As Integer, ByVal height As Integer) As Bitmap Dim resultBmp As New Bitmap(width, height) Using pg = Graphics.FromImage(resultBmp) pg.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias '抗锯齿 pg.DrawString(text, font, Brushes.Black, 0, 0) End Using Return resultBmp End Function
/// <summary> /// 返回指定矩形区域的屏幕图像 /// </summary> /// <param name="rect">指定的矩形区域</param> /// <returns></returns> public Bitmap GetScreenImage(Rectangle rect) { Bitmap resultBmp = new Bitmap(rect.Width, rect.Height); using (Graphics pg = Graphics.FromImage(resultBmp)) { pg.CopyFromScreen(rect.X, rect.Y, 0, 0, new Size(rect.Width, rect.Height)); } return resultBmp; }
/// <summary> /// 返回指定文字生成的位图 /// </summary> /// <param name="text">文本</param> /// <param name="font">字体</param> /// <param name="width">位图宽度</param> /// <param name="height">位图高度</param> /// <returns></returns> public Bitmap GetTextImage(string text, Font font, int width, int height) { Bitmap resultBmp = new Bitmap(width, height); using (pg == Graphics.FromImage(resultBmp)) { pg.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias; //抗锯齿 pg.DrawString(text, font, Brushes.Black, 0, 0); } return resultBmp; }
第二节 图像处理
接下来对模板图像进行处理,以便于下一步的轨迹寻找。
二值化
基于一个阈值 T,大于 T 的像素群设定为白色,小于 T 的像素群设定为黑色
也就是将整个图像呈现出明显的只有黑和白的视觉效果
图2-1 全局二值化
细化
将粗线条细化为细线条(线条宽度通常为一像素)
细化算法是为了找出图像的轮廓,对不同图像的效果不同
图2-2 轮廓化
''' <summary> ''' 提供对位图图像和颜色的一系列操作的对象 ''' </summary> Public Class ImageProcess ''' <summary> ''' 基于RGB根据指定阈值判断两个颜色是否相近 ''' </summary> Public Function CompareRGB(ByVal Color1 As Color, ByVal Color2 As Color, ByVal Distance As Single) As Boolean Dim r As Integer = Int(Color1.R) - Int(Color2.R) Dim g As Integer = Int(Color1.G) - Int(Color2.G) Dim b As Integer = Int(Color1.B) - Int(Color2.B) Dim absDis As Integer = Math.Sqrt(r * r + g * g + b * b) If absDis < Distance Then Return True Else Return False End If End Function ''' <summary> ''' 基于HSB根据指定阈值判断两个颜色是否相近 ''' </summary> Public Function CompareHSB(ByVal Color1 As Color, ByVal Color2 As Color, ByVal Distance As Single) As Boolean '向量距离 'Dim h As Single = (Color1.GetHue - Color2.GetHue) / 360 'Dim s As Single = Color1.GetSaturation - Color2.GetSaturation 'Dim b As Single = Color1.GetBrightness - Color2.GetBrightness 'Dim absDis As Single = Math.Sqrt(h * h + s * s + b * b) 'If absDis < Distance Then ' Return True 'Else ' Return False 'End If '向量夹角 Dim h1 As Single = Color1.GetHue / 360 Dim s1 As Single = Color1.GetSaturation Dim b1 As Single = Color1.GetBrightness Dim h2 As Single = Color2.GetHue / 360 Dim s2 As Single = Color2.GetSaturation Dim b2 As Single = Color2.GetBrightness Dim absDis As Single = (h1 * h2 + s1 * s2 + b1 * b2) / (Math.Sqrt(h1 * h1 + s1 * s1 + b1 * b1) * Math.Sqrt(h2 * h2 + s2 * s2 + b2 * b2)) If absDis > Distance / 5 + 0.8 Then Return True Else Return False End If End Function ''' <summary> ''' 返回指定颜色的中值 ''' </summary> Public Function gethHD(ByVal color1 As Color) Dim HD, r, g, b As Integer r = color1.R g = color1.G b = color1.B HD = (r + g + b) / 3 Return HD End Function ''' <summary> ''' 返回指定位图的颜色数组 ''' </summary> ''' <param name="gBitmap"></param> ''' <returns></returns> Public Function GetColorArr(ByRef gBitmap As Bitmap) As Color(,) Dim TempArr(gBitmap.Width - 1, gBitmap.Height - 1) As Color For i = 0 To gBitmap.Width - 1 For j = 0 To gBitmap.Height - 1 TempArr(i, j) = gBitmap.GetPixel(i, j) Next Next Return TempArr End Function ''' <summary> ''' 返回指定矩形区域的屏幕图像 ''' </summary> ''' <param name="rect">指定的矩形区域</param> ''' <returns></returns> Public Function GetScreenImage(ByVal rect As Rectangle) As Bitmap Dim resultBmp As New Bitmap(rect.Width, rect.Height) Using pg As Graphics = Graphics.FromImage(resultBmp) pg.CopyFromScreen(rect.X, rect.Y, 0, 0, New Size(rect.Width, rect.Height)) End Using Return resultBmp End Function ''' <summary> ''' 返回指定文字生成的位图 ''' </summary> ''' <param name="text">文本</param> ''' <param name="font">字体</param> ''' <param name="width">位图宽度</param> ''' <param name="height">位图高度</param> ''' <returns></returns> Public Function GetTextImage(ByVal text As String, ByVal font As Font, ByVal width As Integer, ByVal height As Integer) As Bitmap Dim resultBmp As New Bitmap(width, height) Using pg = Graphics.FromImage(resultBmp) pg.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias '抗锯齿 pg.DrawString(text, font, Brushes.Black, 0, 0) End Using Return resultBmp End Function ''' <summary> ''' 返回指定图位图的二值化图像 ''' </summary> ''' <param name="gBitmap"></param> ''' <param name="gSplitNum"></param> ''' <returns></returns> Public Function GetThresholdImage(ByVal gBitmap As Bitmap, ByVal gSplitNum As Single, Optional IsHSB As Boolean = False) As Bitmap Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height) Dim ColorArr(,) = GetColorArr(gBitmap) Dim TempHD As Integer Dim IsOverThreshold = Function(ByVal C1 As Color, ByVal gNum As Single) TempHD = gethHD(C1) Return (If(IsHSB, (C1.GetHue / 360 + C1.GetSaturation + C1.GetBrightness) / 3 < gNum, TempHD < gNum)) End Function For i = 0 To gBitmap.Width - 1 For j = 0 To gBitmap.Height - 1 ResultBitmap.SetPixel(i, j, If(IsOverThreshold(ColorArr(i, j), gSplitNum), Color.Black, Color.White)) Next Next Return ResultBitmap End Function ''' <summary> ''' 返回指定位图的轮廓图像 ''' </summary> ''' <param name="gBitmap"></param> ''' <param name="gDistance"></param> ''' <returns></returns> Public Function GetOutLineImage(ByVal gBitmap As Bitmap, ByVal gDistance As Single, Optional IsHSB As Boolean = False) As Bitmap Dim xArray2() As Short = {0, 1, 0, -1} Dim yArray2() As Short = {-1, 0, 1, 0} 'Dim ResultBitmap As New Bitmap(gBitmap) '在原图的基础上绘图 Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height) Dim Color1, Color2 As Color Dim CompareColor = Function(ByVal C1 As Color, ByVal C2 As Color, ByVal Distance As Single) Return If(IsHSB, CompareHSB(Color1, Color2, Distance), CompareRGB(Color1, Color2, Distance)) End Function Dim CompareColorExtra = Function(ByVal C1 As Color, ByVal C2 As Color) Return If(IsHSB, Color1.GetBrightness - Color2.GetBrightness > 0, gethHD(Color1) - gethHD(Color2) > 0) End Function Dim ColorArr(,) = GetColorArr(gBitmap) For i = 1 To gBitmap.Width - 2 For j = 1 To gBitmap.Height - 2 ResultBitmap.SetPixel(i, j, Color.White) Color1 = ColorArr(i, j) For p = 0 To 3 Color2 = ColorArr(i + xArray2(p), j + yArray2(p)) If Not CompareColor(Color1, Color2, gDistance) And CompareColorExtra(Color1, Color2) Then ResultBitmap.SetPixel(i, j, Color.Black) ' ResultBitmap.SetPixel(i, j, ColorArr(i, j)) End If Next Next Next Return ResultBitmap End Function ''' <summary> ''' 返回指定位图的空心图像 ''' </summary> ''' <param name="gBitmap"></param> ''' <returns></returns> Public Function GetAroundImage(gBitmap As Bitmap) Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height) Dim ImageBolArr(,) As Integer = GetImageBol(gBitmap) For i = 0 To gBitmap.Width - 1 For j = 0 To gBitmap.Height - 1 If ImageBolArr(i, j) = 1 AndAlso CheckPointAround(ImageBolArr, i, j) = False Then ResultBitmap.SetPixel(i, j, Color.Black) Else ResultBitmap.SetPixel(i, j, Color.White) End If Next Next Return ResultBitmap End Function ''' <summary> ''' 返回指定位图的反相图像 ''' </summary> ''' <param name="gBitmap"></param> ''' <returns></returns> Public Function GetInvertImage(gBitmap As Bitmap) Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height) Dim ImageBolArr(,) As Integer = GetImageBol(gBitmap) For i = 0 To gBitmap.Width - 1 For j = 0 To gBitmap.Height - 1 If ImageBolArr(i, j) = 1 Then ResultBitmap.SetPixel(i, j, Color.White) Else ResultBitmap.SetPixel(i, j, Color.Black) End If Next Next Return ResultBitmap End Function ''' <summary> ''' 返回指定位图的色块图像 ''' </summary> ''' <param name="gBitmap"></param> ''' <returns></returns> Public Function GetLumpImage(gBitmap As Bitmap, Optional Range As Integer = 10) Dim ResultBitmap As New Bitmap(gBitmap.Width, gBitmap.Height) Dim ColorArr(,) = GetColorArr(gBitmap) Dim R, G, B As Integer For i = 0 To gBitmap.Width - 1 For j = 0 To gBitmap.Height - 1 R = Int(ColorArr(i, j).R / Range) * Range G = Int(ColorArr(i, j).G / Range) * Range B = Int(ColorArr(i, j).B / Range) * Range ResultBitmap.SetPixel(i, j, Color.FromArgb(R, G, B)) Next Next Return ResultBitmap End Function ''' <summary> ''' 返回指定位图的二值化数据 ''' </summary> ''' <param name="gBitmap"></param> ''' <returns></returns> Private Function GetImageBol(ByVal gBitmap As Bitmap) As Integer(,) Dim ResultArr(gBitmap.Width - 1, gBitmap.Height - 1) As Integer For i = 0 To gBitmap.Width - 1 For j = 0 To gBitmap.Height - 1 If Not gBitmap.GetPixel(i, j).Equals(Color.FromArgb(255, 255, 255)) Then ResultArr(i, j) = 1 Else ResultArr(i, j) = 0 End If Next Next Return ResultArr End Function ''' <summary> ''' 检查一个点是否被包围 ''' </summary> ''' <param name="BolArr"></param> ''' <param name="x"></param> ''' <param name="y"></param> ''' <returns></returns> Private Function CheckPointAround(BolArr As Integer(,), ByVal x As Integer, ByVal y As Integer) As Boolean If Not (x > 0 And y > 0 And x < BolArr.GetUpperBound(0) And y < BolArr.GetUpperBound(1)) Then Return True If BolArr(x - 1, y) = 1 And BolArr(x + 1, y) = 1 And BolArr(x, y - 1) = 1 And BolArr(x, y + 1) = 1 Then Return True '当前点为实体内部 Else Return False '当前点为实体边缘 End If End Function End Class
using System; using System.Drawing; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; /// <summary> /// 提供对位图图像和颜色的一系列操作的对象 /// </summary> public class ImageProcess { /// <summary> /// 基于RGB根据指定阈值判断两个颜色是否相近 /// </summary> public bool CompareRGB(Color Color1, Color Color2, float Distance) { int r = Conversion.Int(Color1.R) - Conversion.Int(Color2.R); int g = Conversion.Int(Color1.G) - Conversion.Int(Color2.G); int b = Conversion.Int(Color1.B) - Conversion.Int(Color2.B); int absDis = Math.Sqrt(r * r + g * g + b * b); if (absDis < Distance) { return true; } else { return false; } } /// <summary> /// 基于HSB根据指定阈值判断两个颜色是否相近 /// </summary> public bool CompareHSB(Color Color1, Color Color2, float Distance) { //向量距离 //Dim h As Single = (Color1.GetHue - Color2.GetHue) / 360 //Dim s As Single = Color1.GetSaturation - Color2.GetSaturation //Dim b As Single = Color1.GetBrightness - Color2.GetBrightness //Dim absDis As Single = Math.Sqrt(h * h + s * s + b * b) //If absDis < Distance Then // Return True //Else // Return False //End If //向量夹角 float h1 = Color1.GetHue / 360; float s1 = Color1.GetSaturation; float b1 = Color1.GetBrightness; float h2 = Color2.GetHue / 360; float s2 = Color2.GetSaturation; float b2 = Color2.GetBrightness; float absDis = (h1 * h2 + s1 * s2 + b1 * b2) / (Math.Sqrt(h1 * h1 + s1 * s1 + b1 * b1) * Math.Sqrt(h2 * h2 + s2 * s2 + b2 * b2)); if (absDis > Distance / 5 + 0.8) { return true; } else { return false; } } /// <summary> /// 返回指定颜色的中值 /// </summary> public object gethHD(Color color1) { int HD = 0; int r = 0; int g = 0; int b = 0; r = color1.R; g = color1.G; b = color1.B; HD = (r + g + b) / 3; return HD; } /// <summary> /// 返回指定位图的颜色数组 /// </summary> /// <param name="gBitmap"></param> /// <returns></returns> public Color[,] GetColorArr(ref Bitmap gBitmap) { Color[,] TempArr = new Color[gBitmap.Width, gBitmap.Height]; for (i = 0; i <= gBitmap.Width - 1; i++) { for (j = 0; j <= gBitmap.Height - 1; j++) { TempArr(i, j) = gBitmap.GetPixel(i, j); } } return TempArr; } /// <summary> /// 返回指定矩形区域的屏幕图像 /// </summary> /// <param name="rect">指定的矩形区域</param> /// <returns></returns> public Bitmap GetScreenImage(Rectangle rect) { Bitmap resultBmp = new Bitmap(rect.Width, rect.Height); using (Graphics pg = Graphics.FromImage(resultBmp)) { pg.CopyFromScreen(rect.X, rect.Y, 0, 0, new Size(rect.Width, rect.Height)); } return resultBmp; } /// <summary> /// 返回指定文字生成的位图 /// </summary> /// <param name="text">文本</param> /// <param name="font">字体</param> /// <param name="width">位图宽度</param> /// <param name="height">位图高度</param> /// <returns></returns> public Bitmap GetTextImage(string text, Font font, int width, int height) { Bitmap resultBmp = new Bitmap(width, height); using (pg == Graphics.FromImage(resultBmp)) { pg.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias; //抗锯齿 pg.DrawString(text, font, Brushes.Black, 0, 0); } return resultBmp; } /// <summary> /// 返回指定图位图的二值化图像 /// </summary> /// <param name="gBitmap"></param> /// <param name="gSplitNum"></param> /// <returns></returns> public Bitmap GetThresholdImage(Bitmap gBitmap, float gSplitNum, bool IsHSB = false) { Bitmap ResultBitmap = new Bitmap(gBitmap.Width, gBitmap.Height); [,] ColorArr = GetColorArr(ref gBitmap); int TempHD = 0; dynamic IsOverThreshold = (Color C1, float gNum) => { TempHD = gethHD(C1); return (IsHSB ? (C1.GetHue / 360 + C1.GetSaturation + C1.GetBrightness) / 3 < gNum : TempHD < gNum); }; for (i = 0; i <= gBitmap.Width - 1; i++) { for (j = 0; j <= gBitmap.Height - 1; j++) { ResultBitmap.SetPixel(i, j, IsOverThreshold(ColorArr(i, j), gSplitNum) ? Color.Black : Color.White); } } return ResultBitmap; } /// <summary> /// 返回指定位图的轮廓图像 /// </summary> /// <param name="gBitmap"></param> /// <param name="gDistance"></param> /// <returns></returns> public Bitmap GetOutLineImage(Bitmap gBitmap, float gDistance, bool IsHSB = false) { short[] xArray2 = { 0, 1, 0, -1 }; short[] yArray2 = { -1, 0, 1, 0 }; //Dim ResultBitmap As New Bitmap(gBitmap) '在原图的基础上绘图 Bitmap ResultBitmap = new Bitmap(gBitmap.Width, gBitmap.Height); Color Color1 = default(Color); Color Color2 = default(Color); dynamic CompareColor = (Color C1, Color C2, float Distance) => { return IsHSB ? CompareHSB(Color1, Color2, Distance) : CompareRGB(Color1, Color2, Distance); }; dynamic CompareColorExtra = (Color C1, Color C2) => { return IsHSB ? Color1.GetBrightness - Color2.GetBrightness > 0 : gethHD(Color1) - gethHD(Color2) > 0; }; [,] ColorArr = GetColorArr(ref gBitmap); for (i = 1; i <= gBitmap.Width - 2; i++) { for (j = 1; j <= gBitmap.Height - 2; j++) { ResultBitmap.SetPixel(i, j, Color.White); Color1 = ColorArr(i, j); for (p = 0; p <= 3; p++) { Color2 = ColorArr(i + xArray2[p], j + yArray2[p]); if (!CompareColor(Color1, Color2, gDistance) & CompareColorExtra(Color1, Color2)) { ResultBitmap.SetPixel(i, j, Color.Black); // ResultBitmap.SetPixel(i, j, ColorArr(i, j)) } } } } return ResultBitmap; } /// <summary> /// 返回指定位图的空心图像 /// </summary> /// <param name="gBitmap"></param> /// <returns></returns> public object GetAroundImage(Bitmap gBitmap) { Bitmap ResultBitmap = new Bitmap(gBitmap.Width, gBitmap.Height); int[,] ImageBolArr = GetImageBol(gBitmap); for (i = 0; i <= gBitmap.Width - 1; i++) { for (j = 0; j <= gBitmap.Height - 1; j++) { if (ImageBolArr[i, j] == 1 && CheckPointAround(ImageBolArr, i, j) == false) { ResultBitmap.SetPixel(i, j, Color.Black); } else { ResultBitmap.SetPixel(i, j, Color.White); } } } return ResultBitmap; } /// <summary> /// 返回指定位图的反相图像 /// </summary> /// <param name="gBitmap"></param> /// <returns></returns> public object GetInvertImage(Bitmap gBitmap) { Bitmap ResultBitmap = new Bitmap(gBitmap.Width, gBitmap.Height); int[,] ImageBolArr = GetImageBol(gBitmap); for (i = 0; i <= gBitmap.Width - 1; i++) { for (j = 0; j <= gBitmap.Height - 1; j++) { if (ImageBolArr[i, j] == 1) { ResultBitmap.SetPixel(i, j, Color.White); } else { ResultBitmap.SetPixel(i, j, Color.Black); } } } return ResultBitmap; } /// <summary> /// 返回指定位图的色块图像 /// </summary> /// <param name="gBitmap"></param> /// <returns></returns> public object GetLumpImage(Bitmap gBitmap, int Range = 10) { Bitmap ResultBitmap = new Bitmap(gBitmap.Width, gBitmap.Height); [,] ColorArr = GetColorArr(ref gBitmap); int R = 0; int G = 0; int B = 0; for (i = 0; i <= gBitmap.Width - 1; i++) { for (j = 0; j <= gBitmap.Height - 1; j++) { R = Conversion.Int(ColorArr(i, j).R / Range) * Range; G = Conversion.Int(ColorArr(i, j).G / Range) * Range; B = Conversion.Int(ColorArr(i, j).B / Range) * Range; ResultBitmap.SetPixel(i, j, Color.FromArgb(R, G, B)); } } return ResultBitmap; } /// <summary> /// 返回指定位图的二值化数据 /// </summary> /// <param name="gBitmap"></param> /// <returns></returns> private int[,] GetImageBol(Bitmap gBitmap) { int[,] ResultArr = new int[gBitmap.Width, gBitmap.Height]; for (i = 0; i <= gBitmap.Width - 1; i++) { for (j = 0; j <= gBitmap.Height - 1; j++) { if (!gBitmap.GetPixel(i, j).Equals(Color.FromArgb(255, 255, 255))) { ResultArr[i, j] = 1; } else { ResultArr[i, j] = 0; } } } return ResultArr; } /// <summary> /// 检查一个点是否被包围 /// </summary> /// <param name="BolArr"></param> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> private bool CheckPointAround(int[,] BolArr, int x, int y) { if (!(x > 0 & y > 0 & x < BolArr.GetUpperBound(0) & y < BolArr.GetUpperBound(1))) return true; if (BolArr[x - 1, y] == 1 & BolArr[x + 1, y] == 1 & BolArr[x, y - 1] == 1 & BolArr[x, y + 1] == 1) { return true; //当前点为实体内部 } else { return false; //当前点为实体边缘 } } }
第三节 画笔轨迹
从非黑即白的像素数据中计算出轨迹。
递归循迹
首先将图像的二值化数据保存在一个二维数组里
程序绘图时仅绘制值为 1 的元素所对应的位置
然后寻找画笔起始位置,依次检查每个位置,当对应值为1时该点即为起点
最后递归检查每一个邻居点,同步模拟鼠标操作
空心轨迹
只要某像素位置的上下左右位置均为 1 即认为该点在实体内部
绘制时跳过该像素就可以实现空心(主要用于空心字体的绘制)
''' <summary> ''' 提供由图像循迹生成绘图序列的对象 ''' </summary> Public Class SequenceManager ''' <summary> ''' 绘制序列的集合 ''' </summary> Public SequenceList As List(Of PointSequence) Public Sub New(BolArr(,) As Integer) SequenceList = New List(Of PointSequence) CalculateSequence(BolArr) End Sub ''' <summary> ''' 创建新的序列 ''' </summary> Private Sub CreateNewSequence() SequenceList.Add(New PointSequence) End Sub ''' <summary> ''' 添加新的位置 ''' </summary> Private Sub AddPoint(point As PointF) SequenceList.Last.PointList.Add(point) End Sub Dim xArray() As Integer = {-1, 0, 1, 1, 1, 0, -1, -1} Dim yArray() As Integer = {-1, -1, -1, 0, 1, 1, 1, 0} Dim NewStart As Boolean ''' <summary> ''' 递归循迹 ''' </summary> Private Sub CheckMove(ByRef BolArr(,) As Integer, ByVal x As Integer, ByVal y As Integer, ByVal StepNum As Integer) Application.DoEvents() '处理主线程消息 If StepNum > 10000 Then Return Dim xBound As Integer = BolArr.GetUpperBound(0) Dim yBound As Integer = BolArr.GetUpperBound(1) Dim dx, dy As Integer Dim AroundValue As Integer = GetAroundValue(BolArr, x, y) If AroundValue > 2 AndAlso AroundValue < 8 Then Return End If For i = 0 To 7 dx = x + xArray(i) dy = y + yArray(i) If Not (dx > 0 And dy > 0 And dx < xBound And dy < yBound) Then Return ElseIf BolArr(dx, dy) = 1 Then BolArr(dx, dy) = 0 If NewStart = True Then Me.CreateNewSequence() Me.AddPoint(New PointF(dx, dy)) NewStart = False Else Me.AddPoint(New PointF(dx, dy)) End If CheckMove(BolArr, dx, dy, StepNum + 1) NewStart = True End If Next End Sub ''' <summary> ''' 计算序列 ''' </summary> Private Sub CalculateSequence(BolArr(,) As Integer) Dim xCount As Integer = BolArr.GetUpperBound(0) Dim yCount As Integer = BolArr.GetUpperBound(1) Dim CP As New Point(xCount / 2, yCount / 2) Dim R As Integer = 0 For R = 0 To If(xCount > yCount, xCount, yCount) For Theat = 0 To Math.PI * 2 Step 1 / R Dim dx As Integer = CP.X + R * Math.Cos(Theat) Dim dy As Integer = CP.Y + R * Math.Sin(Theat) If Not (dx > 0 And dy > 0 And dx < xCount And dy < yCount) Then Continue For If BolArr(dx, dy) = 1 Then BolArr(dx, dy) = 0 Me.CreateNewSequence() Me.AddPoint(New PointF(dx, dy)) CheckMove(BolArr, dx, dy, 0) NewStart = True End If Next Next End Sub ''' <summary> ''' 返回指定像素位置的权值 ''' </summary> Private Function GetAroundValue(ByRef BolArr(,) As Integer, ByVal x As Integer, ByVal y As Integer) As Integer Dim dx, dy, ResultValue As Integer Dim xBound As Integer = BolArr.GetUpperBound(0) Dim yBound As Integer = BolArr.GetUpperBound(1) For i = 0 To 7 dx = x + xArray(i) dy = y + yArray(i) If dx > 0 And dy > 0 And dx < xBound And dy < yBound Then If BolArr(dx, dy) = 1 Then ResultValue += 1 End If End If Next Return ResultValue End Function End Class
''' <summary> ''' 表示一条画图曲线的绘制序列 ''' </summary> Public Class PointSequence Public PointList As New List(Of PointF) End Class
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; /// <summary> /// 提供由图像循迹生成绘图序列的对象 /// </summary> public class SequenceManager { /// <summary> /// 绘制序列的集合 /// </summary> public List<PointSequence> SequenceList; public SequenceManager(int[,] BolArr) { SequenceList = new List<PointSequence>(); CalculateSequence(BolArr); } /// <summary> /// 创建新的序列 /// </summary> private void CreateNewSequence() { SequenceList.Add(new PointSequence()); } /// <summary> /// 添加新的位置 /// </summary> private void AddPoint(PointF point) { SequenceList.Last.PointList.Add(point); } int[] xArray = { -1, 0, 1, 1, 1, 0, -1, -1 }; int[] yArray = { -1, -1, -1, 0, 1, 1, 1, 0 }; bool NewStart; /// <summary> /// 递归循迹 /// </summary> private void CheckMove(ref int[,] BolArr, int x, int y, int StepNum) { Application.DoEvents(); //处理主线程消息 if (StepNum > 10000) return; int xBound = BolArr.GetUpperBound(0); int yBound = BolArr.GetUpperBound(1); int dx = 0; int dy = 0; int AroundValue = GetAroundValue(ref BolArr, x, y); if (AroundValue > 2 && AroundValue < 8) { return; } for (i = 0; i <= 7; i++) { dx = x + xArray[i]; dy = y + yArray[i]; if (!(dx > 0 & dy > 0 & dx < xBound & dy < yBound)) { return; } else if (BolArr[dx, dy] == 1) { BolArr[dx, dy] = 0; if (NewStart == true) { this.CreateNewSequence(); this.AddPoint(new PointF(dx, dy)); NewStart = false; } else { this.AddPoint(new PointF(dx, dy)); } CheckMove(ref BolArr, dx, dy, StepNum + 1); NewStart = true; } } } /// <summary> /// 计算序列 /// </summary> private void CalculateSequence(int[,] BolArr) { int xCount = BolArr.GetUpperBound(0); int yCount = BolArr.GetUpperBound(1); Point CP = new Point(xCount / 2, yCount / 2); int R = 0; for (R = 0; R <= xCount > yCount ? xCount : yCount; R++) { for (Theat = 0; Theat <= Math.PI * 2; Theat += 1 / R) { int dx = CP.X + R * Math.Cos(Theat); int dy = CP.Y + R * Math.Sin(Theat); if (!(dx > 0 & dy > 0 & dx < xCount & dy < yCount)) continue; if (BolArr[dx, dy] == 1) { BolArr[dx, dy] = 0; this.CreateNewSequence(); this.AddPoint(new PointF(dx, dy)); CheckMove(ref BolArr, dx, dy, 0); NewStart = true; } } } } /// <summary> /// 返回指定像素位置的权值 /// </summary> private int GetAroundValue(ref int[,] BolArr, int x, int y) { int dx = 0; int dy = 0; int ResultValue = 0; int xBound = BolArr.GetUpperBound(0); int yBound = BolArr.GetUpperBound(1); for (i = 0; i <= 7; i++) { dx = x + xArray[i]; dy = y + yArray[i]; if (dx > 0 & dy > 0 & dx < xBound & dy < yBound) { if (BolArr[dx, dy] == 1) { ResultValue += 1; } } } return ResultValue; } }
using System; using System.Collections.Generic; /// <summary> /// 表示一条画图曲线的绘制序列 /// </summary> public class PointSequence { public List<PointF> PointList = new List<PointF>(); }
第四节 绘图
最后,控制鼠标在画板上依次画出线条序列即可。
光标按键控制
调用 user32.dll 库下的 mouse_event() ,它提供综合的光标击键和光标动作模拟
光标指针移动
System.Windows.Forms.Cursor 对象提供对系统光标的访问
直接对 Cursor.Position 赋值就能控制指针坐标
也可以调用 user32.dll 库下的 SetCursorPos() 实现模拟光标移动
Windows画板的局限性
至此,我们的程序已经可以通过控制光标在 Windows 画板上实现自动绘图
但由于画板短时间内不能响应过多的 Windows 消息,导致画图速度不能过快
同时程序也很难控制画笔的笔触大小与色彩
Private Declare Sub mouse_event Lib "user32" (ByVal dwFlags As Int32, ByVal dx As Int32, ByVal dy As Int32, ByVal cButtons As Int32, ByVal dwExtraInfo As Int32) Private Declare Function SetCursorPos Lib "user32" (ByVal x As Integer, ByVal y As Integer) As Integer ''' <summary> ''' 模拟鼠标左键按下或弹起 ''' </summary> ''' <param name="dx"></param> ''' <param name="dy"></param> ''' <param name="type"></param> Private Sub MouseDownUp(ByVal dx As Integer, ByVal dy As Integer, ByVal type As Boolean) If type Then '按下 mouse_event(&H2, 0, 0, 0, IntPtr.Zero) Else '弹起 mouse_event(&H4, 0, 0, 0, IntPtr.Zero) End If End Sub ''' <summary> ''' 模拟鼠标移动 ''' </summary> ''' <param name="dx"></param> ''' <param name="dy"></param> Private Sub MouseMove(ByVal dx As Integer, ByVal dy As Integer) Cursor.Position = New Point(dx, dy) End Sub
using System.Runtime.InteropServices; [DllImport("user32")] public static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo); private void MouseDownUp(int dx, int dy, bool type) { //按下 if (type) { mouse_event(0x2, 0, 0, 0, IntPtr.Zero); //弹起 } else { mouse_event(0x4, 0, 0, 0, IntPtr.Zero); } } /// <summary> /// 模拟鼠标移动 /// </summary> /// <param name="dx"></param> /// <param name="dy"></param> private void MouseMove(int dx, int dy) { Cursor.Position = new Point(dx, dy); }
视频
附录
后续文章:更优秀的自动绘图程序
后续文章:儿童涂鸦遇上程序绘图