玩转你画我猜(一):程序实现自动绘图

说明

  本文发布时间较早,查看最新动态请关注 GitHub 项目。(2019 年 6 月 注)

准备

  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+ 绘图命令

    ''' 
    ''' 返回指定矩形区域的屏幕图像
    ''' 
    ''' 指定的矩形区域
    ''' 
    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
VB.NET-GetScreenImage
    ''' 
    ''' 返回指定文字生成的位图
    ''' 
    ''' 文本
    ''' 字体
    ''' 位图宽度
    ''' 位图高度
    ''' 
    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
VB.NET-GetTextImage
/// 
/// 返回指定矩形区域的屏幕图像
/// 
/// 指定的矩形区域
/// 
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;
}
C#-GetScreenImage
/// 
/// 返回指定文字生成的位图
/// 
/// 文本
/// 字体
/// 位图宽度
/// 位图高度
/// 
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;
}
C#-GetTextImage

第二节 图像处理

  接下来对模板图像进行处理,以便于下一步的轨迹寻找。

      二值化

  基于一个阈值 T,大于 T 的像素群设定为白色,小于 T 的像素群设定为黑色

  也就是将整个图像呈现出明显的只有黑和白的视觉效果

图2-1 全局二值化

 

      细化

  将粗线条细化为细线条(线条宽度通常为一像素)

  细化算法是为了找出图像的轮廓,对不同图像的效果不同

图2-2 轮廓化

 

''' 
''' 提供对位图图像和颜色的一系列操作的对象
''' 
Public Class ImageProcess
    '''  
    ''' 基于RGB根据指定阈值判断两个颜色是否相近
    '''  
    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
    '''  
    ''' 基于HSB根据指定阈值判断两个颜色是否相近
    '''  
    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
    '''  
    ''' 返回指定颜色的中值
    '''  
    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
    ''' 
    ''' 返回指定位图的颜色数组
    ''' 
    ''' 
    ''' 
    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
    ''' 
    ''' 返回指定矩形区域的屏幕图像
    ''' 
    ''' 指定的矩形区域
    ''' 
    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
    ''' 
    ''' 返回指定文字生成的位图
    ''' 
    ''' 文本
    ''' 字体
    ''' 位图宽度
    ''' 位图高度
    ''' 
    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
    ''' 
    ''' 返回指定图位图的二值化图像
    ''' 
    ''' 
    ''' 
    ''' 
    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
    ''' 
    ''' 返回指定位图的轮廓图像
    ''' 
    ''' 
    ''' 
    ''' 
    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
    ''' 
    ''' 返回指定位图的空心图像
    ''' 
    ''' 
    ''' 
    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
    ''' 
    ''' 返回指定位图的反相图像
    ''' 
    ''' 
    ''' 
    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
    ''' 
    ''' 返回指定位图的色块图像
    ''' 
    ''' 
    ''' 
    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
    ''' 
    ''' 返回指定位图的二值化数据
    ''' 
    ''' 
    ''' 
    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
    ''' 
    ''' 检查一个点是否被包围
    ''' 
    ''' 
    ''' 
    ''' 
    ''' 
    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
VB.NET-ImageProcess
using System;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
/// 
/// 提供对位图图像和颜色的一系列操作的对象
/// 
public class ImageProcess
{
    ///  
    /// 基于RGB根据指定阈值判断两个颜色是否相近
    ///  
    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;
        }
    }
    ///  
    /// 基于HSB根据指定阈值判断两个颜色是否相近
    ///  
    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;
        }
    }
    ///  
    /// 返回指定颜色的中值
    ///  
    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;
    }
    /// 
    /// 返回指定位图的颜色数组
    /// 
    /// 
    /// 
    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;
    }
    /// 
    /// 返回指定矩形区域的屏幕图像
    /// 
    /// 指定的矩形区域
    /// 
    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;
    }
    /// 
    /// 返回指定文字生成的位图
    /// 
    /// 文本
    /// 字体
    /// 位图宽度
    /// 位图高度
    /// 
    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;
    }
    /// 
    /// 返回指定图位图的二值化图像
    /// 
    /// 
    /// 
    /// 
    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;
    }
    /// 
    /// 返回指定位图的轮廓图像
    /// 
    /// 
    /// 
    /// 
    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;
    }
    /// 
    /// 返回指定位图的空心图像
    /// 
    /// 
    /// 
    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;
    }
    /// 
    /// 返回指定位图的反相图像
    /// 
    /// 
    /// 
    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;
    }
    /// 
    /// 返回指定位图的色块图像
    /// 
    /// 
    /// 
    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;
    }
    /// 
    /// 返回指定位图的二值化数据
    /// 
    /// 
    /// 
    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;
    }
    /// 
    /// 检查一个点是否被包围
    /// 
    /// 
    /// 
    /// 
    /// 
    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;
            //当前点为实体边缘
        }
    }
}
C#-ImageProcess

第三节 画笔轨迹

  从非黑即白的像素数据中计算出轨迹。

      递归循迹

  首先将图像的二值化数据保存在一个二维数组里

  程序绘图时仅绘制值为 1 的元素所对应的位置

  然后寻找画笔起始位置,依次检查每个位置,当对应值为1时该点即为起点

  最后递归检查每一个邻居点,同步模拟鼠标操作

      空心轨迹

  只要某像素位置的上下左右位置均为 1 即认为该点在实体内部

  绘制时跳过该像素就可以实现空心(主要用于空心字体的绘制)

''' 
''' 提供由图像循迹生成绘图序列的对象
''' 
Public Class SequenceManager
    ''' 
    ''' 绘制序列的集合
    ''' 
    Public SequenceList As List(Of PointSequence)

    Public Sub New(BolArr(,) As Integer)
        SequenceList = New List(Of PointSequence)
        CalculateSequence(BolArr)
    End Sub
    ''' 
    ''' 创建新的序列
    ''' 
    Private Sub CreateNewSequence()
        SequenceList.Add(New PointSequence)
    End Sub
    ''' 
    ''' 添加新的位置
    ''' 
    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
    ''' 
    ''' 递归循迹
    ''' 
    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
    ''' 
    ''' 计算序列
    ''' 
    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
    ''' 
    ''' 返回指定像素位置的权值
    ''' 
    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
VB.NET-SequenceManager
''' 
''' 表示一条画图曲线的绘制序列
''' 
Public Class PointSequence
    Public PointList As New List(Of PointF)
End Class
VB.NET-PointSequence
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
/// 
/// 提供由图像循迹生成绘图序列的对象
/// 
public class SequenceManager
{
    /// 
    /// 绘制序列的集合
    /// 

    public List SequenceList;
    public SequenceManager(int[,] BolArr)
    {
        SequenceList = new List();
        CalculateSequence(BolArr);
    }
    /// 
    /// 创建新的序列
    /// 
    private void CreateNewSequence()
    {
        SequenceList.Add(new PointSequence());
    }
    /// 
    /// 添加新的位置
    /// 
    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;
    /// 
    /// 递归循迹
    /// 
    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;
            }
        }
    }
    /// 
    /// 计算序列
    /// 
    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;
                }
            }
        }
    }
    /// 
    /// 返回指定像素位置的权值
    /// 
    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;
    }
}
C#-SequenceManager
using System;
using System.Collections.Generic;
/// 
/// 表示一条画图曲线的绘制序列
/// 
public class PointSequence
{
    public List PointList = new List();
}
C#-PointSequence

第四节 绘图

  最后,控制鼠标在画板上依次画出线条序列即可。

  光标按键控制

  调用 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
    ''' 
    ''' 模拟鼠标左键按下或弹起
    ''' 
    ''' 
    ''' 
    ''' 
    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
    ''' 
    ''' 模拟鼠标移动
    ''' 
    ''' 
    ''' 
    Private Sub MouseMove(ByVal dx As Integer, ByVal dy As Integer)
        Cursor.Position = New Point(dx, dy)
    End Sub
VB.NET-MouseControl
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);
    }
}
/// 
/// 模拟鼠标移动
/// 
/// 
/// 
private void MouseMove(int dx, int dy)
{
    Cursor.Position = new Point(dx, dy);
}
C#-MouseControl

视频

  演示视频:黑白线条画 (Bilibili)

  演示视频:古典人物画 (Bilibili)

附录

  后续文章:更优秀的自动绘图程序

  后续文章:儿童涂鸦遇上程序绘图

  后续文章:程序如何画动漫美少女

转载于:https://www.cnblogs.com/experdot/p/4494351.html

你可能感兴趣的:(玩转你画我猜(一):程序实现自动绘图)