说明
本文发布较早,查看最新动态,请关注 GitHub 项目。(2020 年 1 月 注)
准备
全新的图形引擎与 AI 算法,高效流畅地绘出任何一副美丽的图像。
IDE:VisualStudio
Language:VB.NET / C#
Graphics:EDGameEngine
第一节 背景
背景是图画里衬托主体事物的景象。
图1-1 先画个蓝蓝的天空
蓝天、白云和大地,程序最擅长这种色调单一的涂抹了。
第二节 轮廓
轮廓是物体的外周或图形的外框。
图2-2 勾勒人物和衣饰轮廓
现在 AI 要控制笔触大小和颜色,让图像的主体显现出来。
第三节 光影
光影是物体在光的照射下呈现出明与暗的关系。
图3-1 光影提升画面质感
AI 可不懂什么是光影,在上一步的基础上优化细节即可。
第四节 润色
润色是增加物体本身及其周围的色彩。
图4-1 画面润色
这是关键一步,AI需要将丢失的颜色细节补缺回来。
第五节 成型
大功告成!前面所有的步骤都是为这一步铺垫。
图5-1 人物已经栩栩如生啦
事实上 AI 只进行这一步也可以画出完整的图像,但没有过渡会显得生硬。
第六节 算法
算法思路很简单,计算画笔轨迹后一遍遍重绘,感觉上是人类画手的效果。
不再是二值化
因为现在要绘制全彩图像,将图像划分为只有黑和白的效果已经没有什么意义,二值化不再适用
适用的方法是将 RGB 颜色空间划分为若干个颜色子空间,然后逐个处理一幅图像中属于某个子空间的区域
自动循迹
循迹算法没有大的变动,仍是早前博客里贴出的代码
彩色图像线条较短,可以不再计算点周围的权值用来中断轨迹
重绘
程序先选择笔触较大、颜色淡的画笔绘制一遍,然后在这基础上逐步减小笔触并加深色彩
直接按照标准笔触可以一遍成型,但会显得突兀和生硬,毕竟这个AI不是真的在思考如何画一幅图像
Imports System.Numerics
''' <summary>
''' 表示自动循迹并生成绘制序列的AI
''' </summary>
Public Class SequenceAI
''' <summary>
''' 线条序列List
''' </summary>
''' <returns></returns>
Public Property Sequences As List(Of PointSequence)
''' <summary>
''' 扫描方式
''' </summary>
Public Property ScanMode As ScanMode = ScanMode.Rect
Dim xArray() As Integer = {-, , , , , , -, -}
Dim yArray() As Integer = {-, -, -, , , , , }
Dim NewStart As Boolean
''' <summary>
''' 创建并初始化一个可自动生成绘制序列AI的实例
''' </summary>
Public Sub New(BolArr(,) As Integer)
Sequences = New List(Of PointSequence)
CalculateSequence(BolArr)
For Each SubItem In Sequences
SubItem.CalcSize()
Next
End Sub
''' <summary>
''' 新增一个序列
''' </summary>
Private Sub CreateNewSequence()
Sequences.Add(New PointSequence)
End Sub
''' <summary>
''' 在序列List末尾项新增一个点
''' </summary>
Private Sub AddPoint(point As Vector2)
Sequences.Last.Points.Add(point)
End Sub
''' <summary>
''' 计算序列
''' </summary>
Private Sub CalculateSequence(BolArr(,) As Integer)
If ScanMode = ScanMode.Rect Then
ScanRect(BolArr)
Else
ScanCircle(BolArr)
End If
End Sub
''' <summary>
''' 圆形扫描
''' </summary>
''' <param name="BolArr"></param>
Private Sub ScanCircle(BolArr(,) As Integer)
Dim xCount As Integer = BolArr.GetUpperBound()
Dim yCount As Integer = BolArr.GetUpperBound()
Dim CP As New Point(xCount / , yCount / )
Dim R As Integer =
For R = To If(xCount > yCount, xCount, yCount)
For Theat = To Math.PI * Step / R
Dim dx As Integer = CInt(CP.X + R * Math.Cos(Theat))
Dim dy As Integer = CInt(CP.Y + R * Math.Sin(Theat))
If Not (dx > And dy > And dx < xCount And dy < yCount) Then Continue For
If BolArr(dx, dy) = Then
BolArr(dx, dy) =
Me.CreateNewSequence()
Me.AddPoint(New Vector2(dx, dy))
CheckMove(BolArr, dx, dy, )
NewStart = True
End If
Next
Next
End Sub
''' <summary>
''' 矩形扫描
''' </summary>
''' <param name="BolArr"></param>
Private Sub ScanRect(BolArr(,) As Integer)
Dim xCount As Integer = BolArr.GetUpperBound()
Dim yCount As Integer = BolArr.GetUpperBound()
For i = To xCount -
For j = To yCount -
Dim dx As Integer = i
Dim dy As Integer = j
If Not (dx > And dy > And dx < xCount And dy < yCount) Then Continue For
If BolArr(dx, dy) = Then
BolArr(dx, dy) =
Me.CreateNewSequence()
Me.AddPoint(New Vector2(dx, dy))
CheckMove(BolArr, dx, dy, )
NewStart = True
End If
Next
Next
End Sub
''' <summary>
''' 递归循迹算法
''' </summary>
Private Sub CheckMove(ByRef bolArr(,) As Integer, ByVal x As Integer, ByVal y As Integer, ByVal StepNum As Integer)
If StepNum > Then Return
Dim xBound As Integer = bolArr.GetUpperBound()
Dim yBound As Integer = bolArr.GetUpperBound()
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 = To
dx = x + xArray(i)
dy = y + yArray(i)
If Not (dx > And dy > And dx < xBound And dy < yBound) Then
Return
ElseIf bolArr(dx, dy) = Then
bolArr(dx, dy) =
If NewStart = True Then
Me.CreateNewSequence()
Me.AddPoint(New Vector2(dx, dy))
NewStart = False
Else
Me.AddPoint(New Vector2(dx, dy))
End If
CheckMove(bolArr, dx, dy, StepNum + )
NewStart = True
End If
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()
Dim yBound As Integer = BolArr.GetUpperBound()
For i = To
dx = x + xArray(i)
dy = y + yArray(i)
If dx > And dy > And dx < xBound And dy < yBound Then
If BolArr(dx, dy) = Then
ResultValue +=
End If
End If
Next
Return ResultValue
End Function
End Class ''' <summary>
''' 线条扫描方式
''' </summary>
Public Enum ScanMode
''' <summary>
''' 矩形扫描
''' </summary>
Rect
''' <summary>
''' 圆形扫描
''' </summary>
Circle
End Enum
VB.NET-SequenceAI
Imports System.Numerics
''' <summary>
''' 表示由一系列点向量组成的线条
''' </summary>
Public Class PointSequence
Public Property Points As New List(Of Vector2)
Public Property Sizes As Single()
''' <summary>
''' 计算画笔大小
''' </summary>
Public Sub CalcSize()
If Points.Count < Then Exit Sub
Static Mid, PenSize As Single
ReDim Sizes(Points.Count - )
For i = To Points.Count -
Mid = CSng(Math.Abs(i - Points.Count / ))
PenSize = - Mid / Points.Count *
Sizes(i) = PenSize
Next
End Sub
End Class
VB.NET-PointSequence
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Numerics;
/// <summary>
/// 表示自动循迹并生成绘制序列的AI
/// </summary>
public class SequenceAI
{
/// <summary>
/// 线条序列List
/// </summary>
/// <returns></returns>
public List<PointSequence> Sequences { get; set; }
/// <summary>
/// 扫描方式
/// </summary>
public ScanMode ScanMode { get; set; }
int[] xArray = {
-,
,
,
,
,
,
-,
-
};
int[] yArray = {
-,
-,
-,
,
,
,
, };
bool NewStart;
/// <summary>
/// 创建并初始化一个可自动生成绘制序列AI的实例
/// </summary>
public SequenceAI(int[,] BolArr)
{
Sequences = new List<PointSequence>();
CalculateSequence(BolArr);
foreach (object SubItem_loopVariable in Sequences) {
SubItem = SubItem_loopVariable;
SubItem.CalcSize();
}
}
/// <summary>
/// 新增一个序列
/// </summary>
private void CreateNewSequence()
{
Sequences.Add(new PointSequence());
}
/// <summary>
/// 在序列List末尾项新增一个点
/// </summary>
private void AddPoint(Vector2 point)
{
Sequences.Last.Points.Add(point);
}
/// <summary>
/// 计算序列
/// </summary>
private void CalculateSequence(int[,] BolArr)
{
if (ScanMode == ScanMode.Rect) {
ScanRect(BolArr);
} else {
ScanCircle(BolArr);
}
}
/// <summary>
/// 圆形扫描
/// </summary>
/// <param name="BolArr"></param>
private void ScanCircle(int[,] BolArr)
{
int xCount = BolArr.GetUpperBound();
int yCount = BolArr.GetUpperBound();
Point CP = new Point(xCount / , yCount / );
int R = ;
for (R = ; R <= xCount > yCount ? xCount : yCount; R++) {
for (Theat = ; Theat <= Math.PI * ; Theat += / R) {
int dx = Convert.ToInt32(CP.X + R * Math.Cos(Theat));
int dy = Convert.ToInt32(CP.Y + R * Math.Sin(Theat));
if (!(dx > & dy > & dx < xCount & dy < yCount))
continue;
if (BolArr[dx, dy] == ) {
BolArr[dx, dy] = ;
this.CreateNewSequence();
this.AddPoint(new Vector2(dx, dy));
CheckMove(ref BolArr, dx, dy, );
NewStart = true;
}
}
}
}
/// <summary>
/// 矩形扫描
/// </summary>
/// <param name="BolArr"></param>
private void ScanRect(int[,] BolArr)
{
int xCount = BolArr.GetUpperBound();
int yCount = BolArr.GetUpperBound();
for (i = ; i <= xCount - ; i++) {
for (j = ; j <= yCount - ; j++) {
int dx = i;
int dy = j;
if (!(dx > & dy > & dx < xCount & dy < yCount))
continue;
if (BolArr[dx, dy] == ) {
BolArr[dx, dy] = ;
this.CreateNewSequence();
this.AddPoint(new Vector2(dx, dy));
CheckMove(ref BolArr, dx, dy, );
NewStart = true;
}
}
}
}
/// <summary>
/// 递归循迹算法
/// </summary>
private void CheckMove(ref int[,] bolArr, int x, int y, int StepNum)
{
if (StepNum > )
return;
int xBound = bolArr.GetUpperBound();
int yBound = bolArr.GetUpperBound();
int dx = ;
int dy = ;
int AroundValue = GetAroundValue(ref bolArr, x, y);
//根据点权值轨迹将在当前点断开
//If AroundValue > 2 AndAlso AroundValue < 8 Then
//Return
//End If
for (i = ; i <= ; i++) {
dx = x + xArray[i];
dy = y + yArray[i];
if (!(dx > & dy > & dx < xBound & dy < yBound)) {
return;
} else if (bolArr[dx, dy] == ) {
bolArr[dx, dy] = ;
if (NewStart == true) {
this.CreateNewSequence();
this.AddPoint(new Vector2(dx, dy));
NewStart = false;
} else {
this.AddPoint(new Vector2(dx, dy));
}
CheckMove(ref bolArr, dx, dy, StepNum + );
NewStart = true;
}
}
}
/// <summary>
/// 返回点权值
/// </summary>
private int GetAroundValue(ref int[,] BolArr, int x, int y)
{
int dx = ;
int dy = ;
int ResultValue = ;
int xBound = BolArr.GetUpperBound();
int yBound = BolArr.GetUpperBound();
for (i = ; i <= ; i++) {
dx = x + xArray[i];
dy = y + yArray[i];
if (dx > & dy > & dx < xBound & dy < yBound) {
if (BolArr[dx, dy] == ) {
ResultValue += ;
}
}
}
return ResultValue;
}
} /// <summary>
/// 线条扫描方式
/// </summary>
public enum ScanMode
{
/// <summary>
/// 矩形扫描
/// </summary>
Rect,
/// <summary>
/// 圆形扫描
/// </summary>
Circle
}
C#-SequenceAI
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Numerics;
/// <summary>
/// 表示由一系列点向量组成的线条
/// </summary>
public class PointSequence
{
public List<Vector2> Points { get; set; }
public float[] Sizes { get; set; }
float static_CalcSize_Mid;
/// <summary>
/// 计算画笔大小
/// </summary>
float static_CalcSize_PenSize;
public void CalcSize()
{
if (Points.Count < )
return;
Sizes = new float[Points.Count];
for (i = ; i <= Points.Count - ; i++) {
static_CalcSize_Mid = Convert.ToSingle(Math.Abs(i - Points.Count / ));
static_CalcSize_PenSize = - static_CalcSize_Mid / Points.Count * ;
Sizes[i] = static_CalcSize_PenSize;
}
}
}
C#-PointSequence
视频
附录
GitHub:EDGameEngine.AutoDraw
早期博客:程序如何实现自动绘图
早期博客:更优秀的自动绘图程序
创意分享:儿童涂鸦遇上程序绘图