前几天搞了一个控笔视频的程序,原理上一个说了。用这个程序生成了几个视频,结果真的有人看,还有人点赞收藏,比我发减肥视频还要火爆,搞不明白网友为啥对这程序生成的视频比较感兴趣。看下图:

这里又稍微完善了一下,增加背景图,增加手势动画,然后给大家分享一下。先看效果:

下面是代码分享:
首先是窗口,就长这样,代码也不是很多。
窗口代码如下:
cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SmartDraw
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Drawer drawer = new Drawer();
Line line = new Line();
int action;//0,没有操作,1绘制路径,2选择中心点
bool msdown;//鼠标是否按下
private void btnClear_Click(object sender, EventArgs e)
{
drawer.Clear();
line.Clear();//清空路径
}
private void Form1_Load(object sender, EventArgs e)
{
drawer.line = line;//绑定线对象
drawer.pic = pic;//绑定图像控件
drawer.Start();//开始
}
private void btnPath_Click(object sender, EventArgs e)
{
line.Clear();//清空路径
action = 1;//标记绘制路径开始
msdown = false;
btnPath.Text = "绘制中";
btnPath.Enabled = false;
}
private void pic_MouseDown(object sender, MouseEventArgs e)
{
if (action!=1) return;//不是路径绘制直接跳过
msdown = true;//标记鼠标按下
line.AddPoint(e.X,e.Y);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
drawer.Stop();
}
private void pic_MouseMove(object sender, MouseEventArgs e)
{
if (action == 1 && msdown) {
line.AddPoint(e.X, e.Y);
}
}
private void pic_MouseUp(object sender, MouseEventArgs e)
{
if (action != 1) return;
msdown = false;
action = 0;
btnPath.Text = "路径绘制";
btnPath.Enabled = true;
}
private void btnSelectCenter_Click(object sender, EventArgs e)
{
if (action != 0) return;
if (line.Points.Count == 0) {
MessageBox.Show("请先绘制基础曲线");
}
action = 2;//标记选择中心点
btnSelectCenter.Text = "选取中";
btnSelectCenter.Enabled = false;
}
private void pic_Click(object sender, EventArgs e)
{
if (action != 2) return;
action = 0;
btnSelectCenter.Text = "选取中心点";
btnSelectCenter.Enabled = true;
line.CenterX = MousePosition.X -this.Left- pic.Left-8;
line.CenterY = MousePosition.Y -this.Top- pic.Top-30;
}
private void btnDraw_Click(object sender, EventArgs e)
{
drawer.Play();
}
}
}
为了绘制曲线,并存储曲线上每个点的位置,所以定义了一个Line.cs
cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SmartDraw
{
/// <summary>
/// 存储一条曲线,第一条手动绘制的曲线
/// </summary>
public class Line
{
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int CenterX { get; set; }
public int CenterY { get; set; }
public List<Point> Points { get; set; }
private double currentArc;//当前角度。弧度
Point last;
public Line()
{
Points = new List<Point>();
}
public void Clear() {
last.X = -1;
last.Y = -1;
Points.Clear();
}
public void AddPoint(int x,int y) {
if ( x == last.X && y == last.Y) return;
Point p = new Point(x, y);
AddPoint(p);
}
private void AddPoint(Point p) {
if (Points.Count == 0) {//第一个
X = p.X;
Y = p.Y;
Width = 1;
Height = 1;
}
last = p;//记录最后一个
Points.Add(p);
int w = p.X - X;
int h = p.Y - Y;
if (w > Width) Width = w;
if (h > Height) Height = h;
}
/// <summary>
/// 设置旋转角度,用于优化算法,每次线段开始,只需要设置一次就行\
/// <param name="deg">角度</param>
/// </summary>
public void setDeg(int deg)
{
currentArc= deg* Math.PI / 180.0;//转换为角度值
}
/// <summary>
/// 获取某个下标的点,旋转后的坐标值
/// </summary>
/// <param name="index">下标</param>
/// <returns></returns>
public Point Rotate(int index)
{
double x = Points[index].X - CenterX;//相对中心点的坐标
double y = Points[index].Y - CenterY;//相对中心点坐标
double r = Math.Sqrt(x *x+ y * y);//半径
double dg = Math.Atan2(y , x )+currentArc;//原始角度
x = r * Math.Cos(dg)+CenterX;
y = r * Math.Sin(dg)+CenterY;
return new Point((int)x,(int) y);
}
}
}
为了让手有一点动画的效果,以及两条线之间有一个手移动的动画,不是瞬间转移到另外一个线开头,定义一个HandMovie.cs,就是绘制手动画用的,代码:
cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SmartDraw
{
/// <summary>
/// 控制手移动动画的对象
/// </summary>
public class HandMovie
{
public int CurIndex { get; set; }
public bool Moving { get; set; }
public List<Point> path = new List<Point>();
public Point target;
public void SetPath(Point p1, Point p2,int step=15) {
target = p2;//记录目标位置
path.Clear();
CurIndex = 0;
Moving = true;
double disx = (p2.X - p1.X) * 1.0 / step;
double disy = (p2.Y - p1.Y) * 1.0 / step;
for (int i = 0; i < step; i++)
{
Point p = new Point((int)(p1.X + disx * i), (int)(p1.Y + disy * i));
path.Add(p);
}
path.Add(p2);//最后目标加进去
}
public Point NextPoint() {
CurIndex++;
if (CurIndex > path.Count + 5)
{
Moving = false;
return target;
}
else if (CurIndex >= path.Count - 1)
{
return target;
}
return path[CurIndex];
}
/// <summary>
/// 获取当前位置
/// </summary>
/// <returns></returns>
public Point CurPoint()
{
return path[CurIndex];
}
////下面是控制手在移动过程中的动画
///实际上就是用一个png图片,以笔尖为中心,旋转-3°到3度,连续生成的一些列图片,
///绘制的时候按帧绘制就行
/// <summary>
/// 存储手左右移动过的集合
/// </summary>
public List<Image> hands = new List<Image>();
int cHand;
int cStep = 1;
/// <summary>
/// 初始化手部左右摆动动画
/// </summary>
/// <param name="img">手图片</param>
/// <param name="x">中心x</param>
/// <param name="y">中心y</param>
/// <param name="deg">摆动幅度+-x</param>
public void InitHands(Image img, int x, int y, int deg=3) {
int w = img.Width+100;int h = img.Height+200;
// double pp = Math.PI / 180.0;
hands.Clear();
cHand = 0;
cStep = 1;
for (double d = -deg; d < deg; d =d+ 0.2) {
// double arc = d * pp;
Image p = new Bitmap(w, h);
hands.Add(p);
Graphics g = Graphics.FromImage(p);
g.TranslateTransform(x, y); // 设置旋转中心点为图像左上角
g.RotateTransform((float)d); // 旋转90度
//g.TranslateTransform((float)-w, (float)-w); // 调整图像位置以保持完整显示
g.DrawImage(img, new Point(0, 0)); // 绘制旋转后的图像
// g.DrawImage(img,0,0)
g.Dispose();
}
}
/// <summary>
/// 按顺序返回手的图片
/// </summary>
/// <returns></returns>
public Image GetHand()
{
if (cStep == 1)
{
Image img = hands[cHand];
cHand += cStep;
if (cHand >= hands.Count - 1)
{
cHand = hands.Count - 1;
cStep = -1;
}
return img;
}
else {
cStep = -1;
Image img = hands[cHand];
cHand += cStep;
if (cHand <= 0)
{
cHand =0;
cStep = 1;
}
return img;
}
}
}
}
绘制曲线的时候,还需计算出所有的点,所以,定义了一个LineMovie.cs,代码如下:
cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SmartDraw
{
/// <summary>
/// 存储一条曲线的所有点
/// </summary>
class Path
{
List<Point> points = new List<Point>();
public void Add(Point p) {
points.Add(p);
}
public Point Get(int i) {
return points[i];
}
public void Clear()
{
points.Clear();
}
}
/// <summary>
/// 存储所有要绘制的曲线数据
/// </summary>
public class LineMovie
{
List<Path> paths = new List<Path>();
int curPath;//当前线下标
int curPoint;//当前点下标
int pathCount;//线的长度
public bool pathEnd;//标记一条线最后一个画完了
public bool over;//是否结束
/// <summary>
/// 清空
/// </summary>
public void Clear()
{
foreach (Path p in paths) p.Clear();
paths.Clear();
}
/// <summary>
/// 创建动画数据
/// </summary>
/// <param name="startDeg"></param>
/// <param name="step"></param>
/// <param name="maxDeg"></param>
public void CreateMovie(Line line,int startDeg=0,int step=-10,int maxDeg=-350)
{
pathCount = line.Points.Count;
curPath = 0;
curPoint = 0;
pathEnd = false;
over = false;
int currentDeg = startDeg;
while (currentDeg != maxDeg) {
CreateLine(line,currentDeg);
currentDeg += step;
}
//CreateLine(line, maxDeg);//最后一个也加进去
}
/// <summary>
/// 创建旋转点
/// </summary>
/// <param name="line"></param>
/// <param name="deg"></param>
private void CreateLine(Line line,int deg) {
Path l = new Path();
line.setDeg(deg);
for (int i = 0; i < line.Points.Count; i++) {
l.Add(line.Rotate(i));//旋转后的坐标
}
paths.Add(l);//添加到集合
}
/// <summary>
/// 获取一个点
/// </summary>
/// <returns></returns>
public Point GetPoint() {
if (curPath < paths.Count)
{
if (curPoint < pathCount) {
Point p= paths[curPath].Get(curPoint);
Next();
return p;
}
}
over = true;
return new Point(20000,20000);//没有了
}
/// <summary>
/// 移动到下一个点
/// </summary>
public void Next()
{
pathEnd = false;
curPoint++;
if (curPoint > pathCount - 1) {
curPoint = 0;
pathEnd = true;//标记线完了
curPath++;
if (curPath > paths.Count - 1)
{
over = true;
}
}
}
}
}
最后是绘图代码:
cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace SmartDraw
{
/// <summary>
/// 绘制曲线和过程动画
/// </summary>
public class Drawer
{
/// <summary>
/// 要显示的图像控件,由窗口传递进来
/// </summary>
public PictureBox pic { get; set; }
public Image img { get; set; }//实际显示的图像
/// <summary>
/// 被绘制的线对象
/// </summary>
public Line line { get; set; }
/// <summary>
/// 绘制线程
/// </summary>
Thread trd;
private bool run = false;
private int dely = 10;//延迟100ms
private Pen pen = new Pen(Color.FromArgb(180, 100, 100, 100),3);//黑色画笔
private Brush brush = new SolidBrush(Color.Red);
private bool play = false;//标记动画开始
private Graphics g;//绘图对象
Graphics g1;//绘制动画的对象
Image hand;//手图片
Image bg;
Image movie;//绘制动画过程中间图片
LineMovie lineMovie = new LineMovie();//存储动画中间的点数据
Point tempPoint;//临时变量,绘制动画的时候,记录上一个点的位置
HandMovie handMovie = new HandMovie();//控制手移动动画
public Drawer()
{
hand = Image.FromFile(Application.StartupPath + "\\hand.png");//读取图片
bg = Image.FromFile(Application.StartupPath + "\\bg.jpg");//读取背景
handMovie.InitHands(hand, 5, 33);
}
/// <summary>
/// 开始生产要绘制的所有曲线,和点
/// </summary>
/// <param name="sdeg">开始角度</param>
/// <param name="step">每次旋转的角度</param>
/// <param name="max">停止角度</param>
public void Play(int sdeg = 0, int step = -10, int max = -360)
{
if (this.line.Points.Count == 0) return;
g1.Clear(Color.FromArgb(0,0,0,0));//清空画布
lineMovie.CreateMovie(line,sdeg,step,max);//创建动画数据
tempPoint = lineMovie.GetPoint();//第一个点
play = true;
}
public void Clear()
{
play = false;
line.Clear();
lineMovie.Clear();
}
/// <summary>
/// 停止绘制
/// </summary>
public void Stop()
{
if (!run) return;
if (trd != null)
{
try
{
trd.Interrupt();
run = false;
trd = null;
}
catch (Exception e) { }
}
}
/// <summary>
/// 开始绘制
/// </summary>
public void Start()
{
if (run) return;
if (trd == null)
{
trd = new Thread(draw);
trd.IsBackground = true;
run = true;
trd.Start();
}
}
/// <summary>
/// 动画实际绘制动画线程
/// </summary>
private void playMovie(Graphics g)
{
if (!lineMovie.over)
{
if (handMovie.Moving) {//如果某一个线段没有绘制完毕
tempPoint = handMovie.NextPoint();//取当前线段的下一个点
}
else {//绘制图像
Point p2 = lineMovie.GetPoint();//第二个点
g1.DrawLine(pen, tempPoint, p2);//绘制线段
tempPoint = p2;//记录一下
if ( lineMovie.pathEnd)//如果线段绘制完毕了,
{
if (lineMovie.over)//如果所有线段都绘制完了,绘制手移动出画布的动画
{
handMovie.SetPath(tempPoint, new Point(img.Width+10,tempPoint.Y+100));//开始手移动动画
}
else//如果只是当前线段绘制完毕,绘制手移动到下一个线段开始的动画
{
Point target = lineMovie.GetPoint();//开始位置
handMovie.SetPath(tempPoint, target);//开始手移动动画
}
}
}
}
else
{
if (handMovie.Moving)//移动手势动画,这个执行完一会,会给pathEnd设置为false
{ //移动手势
tempPoint = handMovie.NextPoint();
}
}
g.DrawImage(movie, 0, 0);//
g.DrawImage(handMovie.GetHand(), tempPoint.X-7, tempPoint.Y - 71);
}
/// <summary>
/// 绘图线程
/// </summary>
private void draw()
{
while (run)
{
if (pic == null || line == null) break;//直接结束
if (pic.Width == 0 || pic.Height == 0) {
Thread.Sleep(100);
continue;
}
if (img == null || img.Width != pic.Width || img.Height != pic.Height)
{
img = new Bitmap(pic.Width, pic.Height);//创建图片
movie = new Bitmap(pic.Width, pic.Height);//创建图片
g = Graphics.FromImage(img);//获取画布
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g1 = Graphics.FromImage(movie);
g1.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
}
g.DrawImage(bg, 0, 0, pic.Width, pic.Height);
if (play)
{ //绘制动画
playMovie(g);
}
else //绘制原始曲线 ,是手动绘制第一条曲线的路径用
{
//g.Clear(Color.White);//白色
for (int i = 1; i < line.Points.Count; i++)
{//循环绘制
g.DrawLine(pen, line.Points[i - 1], line.Points[i]);//绘制直线
}
g.FillEllipse(brush, line.CenterX - 3, line.CenterY - 3, 7, 7);
}
try
{
//以下用于在界面上更新图像
pic.Invoke(new Action(() =>
{
pic.Image = img;
pic.Refresh();
}
));
}
catch (Exception) {
return;
}
Thread.Sleep(dely);//等待
}
//没有图片控件的时候,线程结束。
run = false;
trd = null;
}
}
}
文末附上git地址