C#生成控笔视频,完整版

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

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

下面是代码分享:

首先是窗口,就长这样,代码也不是很多。

窗口代码如下:

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地址

https://gitee.com/qujia/smart-drawer.git

相关推荐
ajassi20003 小时前
开源 C# 快速开发(三)复杂控件
开发语言·开源·c#
WangMing_X4 小时前
C#上位机软件:2.1 .NET项目解决方案的作用
开发语言·c#
Sammyyyyy7 小时前
Go与C# 谁才更能节省内存?
java·golang·c#
Dream achiever7 小时前
7.WPF 的 TextBox 和 TextBlock 控件
开发语言·c#·wpf
爱吃小胖橘7 小时前
Unity-动画子状态机
3d·unity·c#·游戏引擎
大飞pkz8 小时前
【设计模式】适配器模式
开发语言·设计模式·c#·适配器模式
大飞pkz8 小时前
【设计模式】外观模式
开发语言·设计模式·c#·外观模式
Humbunklung9 小时前
C# 压缩解压文件的常用方法
前端·c#·压缩解压
mudtools10 小时前
.NET操作Excel:高效数据读写与批量操作
c#·.net·excel·wps