WPF+Canvas实现平滑笔迹的示例代码

 更新时间:2022年09月05日 14:33:37   作者:小封(邝攀升)  
这篇文章主要介绍了如何利用WPF+Canvas实现平滑笔迹效果,文中的示例代码讲解详细,对我们学习或工作有一定帮助,需要的可以参考一下

实现思路

收集路径点集。

平均采样路径点集。

将路径点集转为 LineB

把 LineB 数据传给 Path

实现效果

实现代码

1)Vector2D.cs 代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{
    public class Vector2D
    {
        public double X { get; set; } = 0;
        public double Y { get; set; } = 0;
       
        /// <summary>
        /// 向量的模
        /// </summary>
        public double Mold
        {
            get
            {
                //自身各分量平方运算.
                double X = this.X * this.X;
                double Y = this.Y * this.Y;
                return Math.Sqrt(X + Y);//开根号,最终返回向量的长度/模/大小.
            }
        }
        /// <summary>
        /// 单位向量
        /// </summary>
        public Vector2D UnitVector
        {
            get
            {
                double sumSquares = (X * X) + (Y * Y);
                return new Vector2D(X / Math.Sqrt(sumSquares), Y / Math.Sqrt(sumSquares));
            }
        }

        public Vector2D()
        {

        }
        public Vector2D(double x, double y)
        {
            X = x;
            Y = y;
        }
        public Vector2D(System.Windows.Point point)
        {
            X = point.X;
            Y = point.Y;
        }

        public void Offset(double angle, double distance, AngleType angleType = AngleType.Radian)
        {
            var vector2D = Vector2D.CalculateVectorOffset(this, angle, distance, angleType);
            X = vector2D.X;
            Y = vector2D.Y;
        }
        public void Rotate(double angle, Vector2D vectorCenter = null, AngleType angleType = AngleType.Radian)
        {
            vectorCenter = vectorCenter == null ? this : vectorCenter;
            var vector2D = Vector2D.CalculateVectorRotation(this, vectorCenter, angle, angleType);
            X = vector2D.X;
            Y = vector2D.Y;
        }

        #region 静态方法
        /// <summary>
        /// 计算两个向量之间的距离
        /// </summary>
        public static double CalculateVectorDistance(Vector2D vector2DA, Vector2D vector2DB)
        {
            Vector2D vector2D = vector2DA - vector2DB;
            return vector2D.Mold;
        }

        /// <summary>
        /// 计算两点夹角,右侧X轴线为0度,向下为正,向上为负
        /// </summary>
        public static double IncludedAngleXAxis(Vector2D vector2DA, Vector2D vector2DB, AngleType angleType = AngleType.Radian)
        {
            double radian = Math.Atan2(vector2DB.Y - vector2DA.Y, vector2DB.X - vector2DA.X); //弧度:1.1071487177940904
            return angleType == AngleType.Radian ? radian : ComputingHelper.RadianToAngle(radian);
        }

        /// <summary>
        /// 计算两点夹角,下侧Y轴线为0度,向右为正,向左为负
        /// </summary>
        public static double IncludedAngleYAxis(Vector2D vector2DA, Vector2D vector2DB, AngleType angleType = AngleType.Radian)
        {
            double radian = Math.Atan2(vector2DB.X - vector2DA.X, vector2DB.Y - vector2DA.Y); //弧度:0.46364760900080609
            return angleType == AngleType.Radian ? radian : ComputingHelper.RadianToAngle(radian);
        }

        /// <summary>
        /// 偏移向量到指定角度,指定距离
        /// </summary>
        public static Vector2D CalculateVectorOffset(Vector2D vector2D, double angle, double distance, AngleType angleType = AngleType.Radian)
        {
            Vector2D pointVector2D = new Vector2D();
            if (angleType == AngleType.Angle)
            {
                angle = angle / (180 / Math.PI);//角度转弧度
            }
            double width = Math.Cos(Math.Abs(angle)) * distance;
            double height = Math.Sin(Math.Abs(angle)) * distance;
            if(angle <= Math.PI && angle >= 0)
            //if (angle is <= Math.PI and >= 0)
            {
                pointVector2D.X = vector2D.X - width;
                pointVector2D.Y = vector2D.Y - height;
            }
            if (angle >= (-Math.PI) && angle <= 0)
            //if (angle is >= (-Math.PI) and <= 0)
            {
                pointVector2D.X = vector2D.X - width;
                pointVector2D.Y = vector2D.Y + height;
            }
            return pointVector2D;
        }

        /// <summary>
        /// 围绕一个中心点,旋转一个向量,相对旋转
        /// </summary>
        public static Vector2D CalculateVectorRotation(Vector2D vector2D, Vector2D vectorCenter, double radian, AngleType angleType = AngleType.Radian)
        {
            radian = angleType == AngleType.Radian ? radian : ComputingHelper.RadianToAngle(radian);
            double x1 = (vector2D.X - vectorCenter.X) * Math.Sin(radian) + (vector2D.Y - vectorCenter.Y) * Math.Cos(radian) + vectorCenter.X;
            double y1 = -(vector2D.X - vectorCenter.X) * Math.Cos(radian) + (vector2D.Y - vectorCenter.Y) * Math.Sin(radian) + vectorCenter.Y;
            return new Vector2D(x1, y1);
        }
        public static Vector2D CalculateVectorCenter(Vector2D vector2DA, Vector2D vector2DB)
        {
            return new Vector2D((vector2DA.X + vector2DB.X) / 2, (vector2DA.Y + vector2DB.Y) / 2);
        }

        /// <summary>
        /// 判断坐标点是否在多边形区域内,射线法
        /// </summary>
        public static bool IsPointPolygonalArea(Vector2D vector2D, List<Vector2D> aolygonaArrayList)
        {
            var N = aolygonaArrayList.Count;
            var boundOrVertex = true; //如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
            var crossNumber = 0; //x的交叉点计数
            var precision = 2e-10; //浮点类型计算时候与0比较时候的容差
            Vector2D p1, p2; //neighbour bound vertices
            var p = vector2D; //测试点
            p1 = aolygonaArrayList[0]; //left vertex        
            for (var i = 1; i <= N; ++i)
            {
                //check all rays            
                if (p.X.Equals(p1.X) && p.Y.Equals(p1.Y))
                {
                    return boundOrVertex; //p is an vertex
                }

                p2 = aolygonaArrayList[i % N]; //right vertex            
                if (p.X < Math.Min(p1.X, p2.X) || p.X > Math.Max(p1.X, p2.X))
                {
                    //ray is outside of our interests                
                    p1 = p2;
                    continue; //next ray left point
                }

                if (p.X > Math.Min(p1.X, p2.X) && p.X < Math.Max(p1.X, p2.X))
                {
                    //ray is crossing over by the algorithm (common part of)
                    if (p.Y <= Math.Max(p1.Y, p2.Y))
                    {
                        //x is before of ray                    
                        if (p1.X == p2.X && p.Y >= Math.Min(p1.Y, p2.Y))
                        {
                            //overlies on a horizontal ray
                            return boundOrVertex;
                        }

                        if (p1.Y == p2.Y)
                        {
                            //ray is vertical                        
                            if (p1.Y == p.Y)
                            {
                                //overlies on a vertical ray
                                return boundOrVertex;
                            }
                            else
                            {
                                //before ray
                                ++crossNumber;
                            }
                        }
                        else
                        {
                            //cross point on the left side                        
                            var xinters =
                                (p.X - p1.X) * (p2.Y - p1.Y) / (p2.X - p1.X) +
                                p1.Y; //cross point of Y                        
                            if (Math.Abs(p.Y - xinters) < precision)
                            {
                                //overlies on a ray
                                return boundOrVertex;
                            }

                            if (p.Y < xinters)
                            {
                                //before ray
                                ++crossNumber;
                            }
                        }
                    }
                }
                else
                {
                    //special case when ray is crossing through the vertex                
                    if (p.X == p2.X && p.Y <= p2.Y)
                    {
                        //p crossing over p2                    
                        var p3 = aolygonaArrayList[(i + 1) % N]; //next vertex                    
                        if (p.X >= Math.Min(p1.X, p3.X) && p.X <= Math.Max(p1.X, p3.X))
                        {
                            //p.X lies between p1.X & p3.X
                            ++crossNumber;
                        }
                        else
                        {
                            crossNumber += 2;
                        }
                    }
                }
                p1 = p2; //next ray left point
            }
            if (crossNumber % 2 == 0)
            {
                //偶数在多边形外
                return false;
            }
            else
            {
                //奇数在多边形内
                return true;
            }
        }

        /// <summary>
        /// 判断一个点是否在一条边内
        /// </summary>
      
        public static bool IsPointEdge(Vector2D point, Vector2D startPoint, Vector2D endPoint)
        {
            return (point.X - startPoint.X) * (endPoint.Y - startPoint.Y) == (endPoint.X - startPoint.X) * (point.Y - startPoint.Y)
                && Math.Min(startPoint.X, endPoint.X) <= point.X && point.X <= Math.Max(startPoint.X, endPoint.X)
                && Math.Min(startPoint.Y, endPoint.Y) <= point.Y && point.Y <= Math.Max(startPoint.Y, endPoint.Y);
        }
        #endregion 静态方法

        #region 运算符重载
        /// <summary>
        /// 重载运算符,和运算,可以用来计算两向量距离
        /// </summary>
        public static Vector2D operator +(Vector2D vector2DA, Vector2D vector2DB)
        {
            Vector2D vector2D = new Vector2D();
            vector2D.X = vector2DA.X + vector2DB.X;
            vector2D.Y = vector2DA.Y + vector2DB.Y;
            return vector2D;
        }

        /// <summary>
        /// 重载运算符,差运算,可以用来计算两向量距离
        /// </summary>
        public static Vector2D operator -(Vector2D vector2DA, Vector2D vector2DB)
        {
            Vector2D vector2D = new Vector2D();
            vector2D.X = vector2DA.X - vector2DB.X;
            vector2D.Y = vector2DA.Y - vector2DB.Y;
            return vector2D;
        }

        /// <summary>
        /// 重载运算符,差运算,可以用来计算两向量距离
        /// </summary>
        public static Vector2D operator -(Vector2D vector2D, double _float)
        {
            return new Vector2D(vector2D.X - _float, vector2D.Y - _float);
        }

        /// <summary>
        /// 重载运算符,点积运算,可以用来计算两向量夹角
        /// </summary>
        public static double operator *(Vector2D vector2DA, Vector2D vector2DB)
        {
            return (vector2DA.X * vector2DB.X) + (vector2DA.Y * vector2DB.Y);
        }
        public static double operator *(Vector2D vector2D, double _float)
        {
            return (vector2D.X * _float) + (vector2D.Y * _float);
        }

        /// <summary>
        /// 重载运算符,点积运算,可以用来计算两向量夹角
        /// </summary>
        public static double operator /(Vector2D vector2D, double para)
        {
            return (vector2D.X / para) + (vector2D.Y / para);
        }

        /// <summary>
        /// 重载运算符
        /// </summary>
        
        public static bool operator >=(Vector2D vector2D, double para)
        {
            if (vector2D.Mold >= para)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public static bool operator <=(Vector2D vector2D, double para)
        {
            if (vector2D.Mold <= para)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public static bool operator >(Vector2D vector2D, double para)
        {
            if (vector2D.Mold > para)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public static bool operator <(Vector2D vector2D, double para)
        {
            if (vector2D.Mold < para)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        #endregion 运算符重载

        #region 隐式转换
        /// <summary>
        /// 重载隐式转换,可以直接使用Point
        /// </summary>
        /// <param name="v"></param>
        public static implicit operator Vector2D(System.Windows.Point v)//隐式转换
        {
            return new Vector2D(v.X, v.Y);
        }
        /// <summary>
        /// 重载隐式转换,可以直接使用Point
        /// </summary>
        /// <param name="v"></param>
        public static implicit operator System.Windows.Point(Vector2D v)//隐式转换
        {
            return new System.Windows.Point(v.X, v.Y);
        }
        /// <summary>
        /// 重载隐式转换,可以直接使用double
        /// </summary>
        /// <param name="v"></param>
        public static implicit operator Vector2D(double v)//隐式转换
        {
            return new Vector2D(v, v);
        }
        #endregion 隐式转换

        #region ToString
        public override string ToString()
        {
            return X.ToString() + "," + Y.ToString();
        }
        public string ToString(string symbol)
        {
            return X.ToString() + symbol + Y.ToString();
        }
        public string ToString(string sender, string symbol)
        {
            return X.ToString(sender) + symbol + Y.ToString(sender);
        }
        #endregion
    }
    public enum AngleType 
    {
        Angle,
        Radian
    }
}

2)ComputingHelper.cs 代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{
    public static class ComputingHelper
    {
        public static double AngleToRadian(double angle)
        {
            return angle * (Math.PI / 180);
        }
        public static double RadianToAngle(double radian)
        {
            return radian * (180 / Math.PI);
        }

        /// <summary>
        /// 将一个值从一个范围映射到另一个范围
        /// </summary>
        public static double RangeMapping(double inputValue, double enterLowerLimit, double enterUpperLimit, double outputLowerLimit, double OutputUpperLimit, CurveType curveType = CurveType.None)
        {
            var percentage = (enterUpperLimit - inputValue) / (enterUpperLimit - enterLowerLimit);
            switch (curveType)
            {
                case CurveType.Sine:
                    percentage = Math.Sin(percentage);
                    break;
                case CurveType.CoSine:
                    percentage = Math.Cos(percentage);
                    break;
                case CurveType.Tangent:
                    percentage = Math.Tan(percentage);
                    break;
                case CurveType.Cotangent:
                    percentage = Math.Atan(percentage);
                    break;
                default:
                    break;
            }
            double outputValue = OutputUpperLimit - ((OutputUpperLimit - outputLowerLimit) * percentage);

            return outputValue;
        }

        public static string ByteToKB(double _byte)
        {
            List<string> unit = new List<string>() { "B", "KB", "MB", "GB", "TB", "P", "PB" };
            int i = 0;
            while (_byte > 1024)
            {
                _byte /= 1024;
                i++;
            }
            _byte = Math.Round(_byte, 3);//保留三位小数
            return _byte + unit[i];
        }

        /// <summary>
        /// 缩短一个数组,对其进行平均采样
        /// </summary>
        
        public static double[] AverageSampling(double[] sourceArray, int number)
        {
            if (sourceArray.Length <= number)
            {
                return sourceArray;
                //throw new Exception("新的数组必须比原有的要小!");
            }
            double[] arrayList = new double[number];
            double stride = (double)sourceArray.Length / number;
            for (int i = 0, jIndex = 0; i < number; i++, jIndex++)
            {
                double strideIncrement = i * stride;
                strideIncrement = Math.Round(strideIncrement, 6);
                double sum = 0;
                int firstIndex = (int)(strideIncrement);
                double firstDecimal = strideIncrement - firstIndex;

                int tailIndex = (int)(strideIncrement + stride);
                double tailDecimal = (strideIncrement + stride) - tailIndex;

                if (firstDecimal != 0)
                    sum += sourceArray[firstIndex] * (1 - firstDecimal);

                if (tailDecimal != 0 && tailIndex != sourceArray.Length)
                    sum += sourceArray[tailIndex] * (tailDecimal);

                int startIndex = firstDecimal == 0 ? firstIndex : firstIndex + 1;
                int endIndex = tailIndex;

                for (int j = startIndex; j < endIndex; j++)
                    sum += sourceArray[j];

                arrayList[jIndex] = sum / stride;
            }
            return arrayList;
        }
        public static List<Vector2D> AverageSampling(List<Vector2D> sourceArray, int number)
        {
            if (sourceArray.Count <= number - 2)
            {
                return sourceArray;
            }
            double[] x = new double[sourceArray.Count];
            double[] y = new double[sourceArray.Count];
            for (int i = 0; i < sourceArray.Count; i++)
            {
                x[i] = sourceArray[i].X;
                y[i] = sourceArray[i].Y;
            }

            double[] X = AverageSampling(x, number - 2);
            double[] Y = AverageSampling(y, number - 2);

            List<Vector2D> arrayList = new List<Vector2D>();
            for (int i = 0; i < number - 2; i++)
            {
                arrayList.Add(new Vector2D(X[i], Y[i]));
            }

            arrayList.Insert(0, sourceArray[0]);//添加首
            arrayList.Add(sourceArray[sourceArray.Count - 1]);//添加尾

            return arrayList;
        }
    }
    public enum CurveType 
    {
        Sine,
        CoSine,
        Tangent,
        Cotangent,
        None
    }
}

3)LineB.cs 代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;

namespace WPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{
    public class LineB
    {
        private List<Vector2D> _vector2DList = new List<Vector2D>();
        public List<Vector2D> Vector2DList
        {
            get { return _vector2DList; }
            set
            {
                _vector2DList = value;
            }
        }

        private List<BezierCurve> _bezierCurveList = new List<BezierCurve>();
        public List<BezierCurve> BezierCurveList
        {
            get { return _bezierCurveList; }
            private set { _bezierCurveList = value; }
        }


        private double _tension = 0.618;
        public double Tension
        {
            get { return _tension; }
            set
            {
                _tension = value;
                if (_tension > 10)
                    _tension = 10;
                if (_tension < 0)
                    _tension = 0;
            }
        }

        private bool _isClosedCurve = true;
        public bool IsClosedCurve
        {
            get { return _isClosedCurve; }
            set { _isClosedCurve = value; }
        }

        private string _pathData = string.Empty;
        public string PathData
        {
            get
            {
                if (_pathData == string.Empty)
                {
                    _pathData = Vector2DToBezierCurve();
                }
                return _pathData;
            }
        }

        private string Vector2DToBezierCurve() 
        {
            if (Vector2DList.Count < 3)
                return string.Empty;

            BezierCurveList.Clear();
            for (int i = 0; i < Vector2DList.Count; i++)
            {
                int pointTwoIndex = i + 1 < Vector2DList.Count ? i + 1 : 0;
                int pointThreeIndex = i + 2 < Vector2DList.Count ? i + 2 : i + 2 - Vector2DList.Count;
                Vector2D vector2D1 = Vector2DList[i];
                Vector2D vector2D2 = Vector2DList[pointTwoIndex];
                Vector2D vector2D3 = Vector2DList[pointThreeIndex];

                Vector2D startVector2D = Vector2D.CalculateVectorCenter(vector2D1, vector2D2);
                double startAngle = Vector2D.IncludedAngleXAxis(vector2D1, vector2D2);
                double startDistance = Vector2D.CalculateVectorDistance(startVector2D, vector2D2) * (1 - Tension);
                Vector2D startControlPoint = Vector2D.CalculateVectorOffset(vector2D2, startAngle, startDistance);

                Vector2D endVector2D = Vector2D.CalculateVectorCenter(vector2D2, vector2D3);
                double endAngle = Vector2D.IncludedAngleXAxis(endVector2D, vector2D2);
                double endDistance = Vector2D.CalculateVectorDistance(endVector2D, vector2D2) * (1 - Tension);
                Vector2D endControlPoint = Vector2D.CalculateVectorOffset(endVector2D, endAngle, endDistance);

                BezierCurve bezierCurve = new BezierCurve();
                bezierCurve.StartVector2D = startVector2D;
                bezierCurve.StartControlPoint = startControlPoint;
                bezierCurve.EndVector2D = endVector2D;
                bezierCurve.EndControlPoint = endControlPoint;
                BezierCurveList.Add(bezierCurve);
            }
            if (!IsClosedCurve)
            {
                BezierCurveList[0].StartVector2D = Vector2DList[0];
                BezierCurveList.RemoveAt(BezierCurveList.Count - 1);
                BezierCurveList[BezierCurveList.Count - 1].EndVector2D = Vector2DList[Vector2DList.Count - 1];
                BezierCurveList[BezierCurveList.Count - 1].EndControlPoint = BezierCurveList[BezierCurveList.Count - 1].EndVector2D;
            }
            string path = $"M {BezierCurveList[0].StartVector2D.ToString()} ";
            foreach (var item in BezierCurveList)
            {
                path += $"C {item.StartControlPoint.ToString(" ")},{item.EndControlPoint.ToString(" ")},{item.EndVector2D.ToString(" ")} ";
            }
            return path;
        }

        public LineB()
        {
        }
        public LineB(List<Vector2D> verVector2DList, bool isClosedCurve = true)
        {
            this.Vector2DList = verVector2DList;
            this.IsClosedCurve = isClosedCurve;
        }

        /// <summary>
        /// 重载隐式转换,可以直接使用Point
        /// </summary>
        /// <param name="v"></param>
        public static implicit operator Geometry(LineB lineB)//隐式转换
        {
            return Geometry.Parse(lineB.PathData);
        }
    }
    public class BezierCurve
    {
        private Vector2D _startVector2D = new Vector2D(0, 0);
        public Vector2D StartVector2D
        {
            get { return _startVector2D; }
            set { _startVector2D = value; }
        }

        private Vector2D _startControlPoint = new Vector2D(0, 100);
        public Vector2D StartControlPoint
        {
            get { return _startControlPoint; }
            set { _startControlPoint = value; }
        }

        private Vector2D _endControlPoint = new Vector2D(100, 0);
        public Vector2D EndControlPoint
        {
            get { return _endControlPoint; }
            set { _endControlPoint = value; }
        }

        private Vector2D _endVector2D = new Vector2D(100, 100);
        public Vector2D EndVector2D
        {
            get { return _endVector2D; }
            set { _endVector2D = value; }
        }


    }
}

4)CanvasHandWritingExample.xaml 代码如下

<UserControl x:Class="WPFDevelopers.Samples.ExampleViews.CanvasHandWriting.CanvasHandWritingExample"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews.CanvasHandWriting"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="Foreground" Value="{StaticResource PrimaryTextSolidColorBrush}" />
        </Style>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Margin="4">
            <TextBlock Text="张力:" VerticalAlignment="Center"/>
            <TextBox Text="{Binding Tension,RelativeSource={RelativeSource AncestorType=local:CanvasHandWritingExample}}"/>
            <Slider Width="100" SmallChange="0.01" 
                    Value="{Binding Tension,RelativeSource={RelativeSource AncestorType=local:CanvasHandWritingExample}}" Maximum="1" 
                    VerticalAlignment="Center" 
                    Margin="5,0"/>
            <TextBlock  Text="平滑采样:" VerticalAlignment="Center"/>
            <TextBox Text="{Binding SmoothSampling,RelativeSource={RelativeSource AncestorType=local:CanvasHandWritingExample}}"
                     Margin="5,0"/>
            <Slider Value="{Binding SmoothSampling,RelativeSource={RelativeSource AncestorType=local:CanvasHandWritingExample}}"
                    Width="100" 
                    VerticalAlignment="Center" 
                    SmallChange="0.01" Maximum="1" 
                    TickFrequency="0.1"/>
            <CheckBox Content="橡皮擦" 
                      VerticalAlignment="Center"
                      Margin="5,0"
                      IsChecked="{Binding IsEraser,RelativeSource={RelativeSource AncestorType=local:CanvasHandWritingExample}}"/>
            <Button Content="清空画布" Click="btnClertCanvas_Click"/>
        </StackPanel>
        <Canvas x:Name="drawingCanvas" 
                Grid.Row="1" Background="Black" 
                PreviewMouseLeftButtonDown="DrawingCanvas_PreviewMouseLeftButtonDown"
                PreviewMouseMove="DrawingCanvas_PreviewMouseMove"
                PreviewMouseLeftButtonUp="DrawingCanvas_PreviewMouseLeftButtonUp"/>
          
    </Grid>
</UserControl>

5)CanvasHandWritingExample.xaml.cs 代码如下

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{
    /// <summary>
    ///     CanvasHandWritingExample.xaml 的交互逻辑
    /// </summary>
    public partial class CanvasHandWritingExample : UserControl
    {
        public static readonly DependencyProperty TensionProperty =
            DependencyProperty.Register("Tension", typeof(double), typeof(CanvasHandWritingExample),
                new PropertyMetadata(0.618));

        public static readonly DependencyProperty SmoothSamplingProperty =
            DependencyProperty.Register("SmoothSampling", typeof(double), typeof(CanvasHandWritingExample),
                new UIPropertyMetadata(OnSmoothSamplingChanged));

        public static readonly DependencyProperty IsEraserProperty =
            DependencyProperty.Register("IsEraser", typeof(bool), typeof(CanvasHandWritingExample),
                new PropertyMetadata(false));

        private readonly Dictionary<Path, List<Vector2D>> _PathVector2DDictionary ;

        volatile bool _IsStart = false;
        Path _DrawingPath = default;

        private static void OnSmoothSamplingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var mWindow = (CanvasHandWritingExample)d;
            foreach (var item in mWindow._PathVector2DDictionary.Keys)
            {
                mWindow.DrawLine(item);
            }
        }
        public CanvasHandWritingExample()
        {
            InitializeComponent();
            _PathVector2DDictionary = new Dictionary<Path, List<Vector2D>>();
            SmoothSampling = 0.8;
        }

        private void DrawingCanvas_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _IsStart = true;
            _DrawingPath = new Path()
            {
                StrokeDashCap = PenLineCap.Round,
                StrokeStartLineCap = PenLineCap.Round,
                StrokeEndLineCap = PenLineCap.Round,
                StrokeLineJoin = PenLineJoin.Round,
            };

            if (IsEraser)
            {
                _DrawingPath.Stroke = new SolidColorBrush(Colors.Black);
                _DrawingPath.StrokeThickness = 40;
            }
            else
            {
                var random = new Random();
                var strokeBrush = new SolidColorBrush(Color.FromRgb((byte)random.Next(200, 255), (byte)random.Next(0, 255), (byte)random.Next(0, 255)));
                _DrawingPath.Stroke = strokeBrush;
                _DrawingPath.StrokeThickness = 10;
            }
            _PathVector2DDictionary.Add(_DrawingPath, new List<Vector2D>());
            drawingCanvas.Children.Add(_DrawingPath);
        }

        private void DrawingCanvas_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _IsStart = false;
            _DrawingPath = default;
        }

        private void DrawingCanvas_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (!_IsStart)
                return;

            if (_DrawingPath is null)
                return;

            Vector2D currenPoint = e.GetPosition(drawingCanvas);
            if (currenPoint.X < 0 || currenPoint.Y < 0)
                return;

            if (currenPoint.X > drawingCanvas.ActualWidth || currenPoint.Y > drawingCanvas.ActualHeight)
                return;

            if (_PathVector2DDictionary[_DrawingPath].Count > 0)
            {
                if (Vector2D.CalculateVectorDistance(currenPoint, _PathVector2DDictionary[_DrawingPath][_PathVector2DDictionary[_DrawingPath].Count - 1]) > 1)
                    _PathVector2DDictionary[_DrawingPath].Add(e.GetPosition(drawingCanvas));
            }
            else
                _PathVector2DDictionary[_DrawingPath].Add(e.GetPosition(drawingCanvas));

            DrawLine(_DrawingPath);
        }


        public double Tension
        {
            get => (double)GetValue(TensionProperty);
            set => SetValue(TensionProperty, value);
        }

        public double SmoothSampling
        {
            get => (double)GetValue(SmoothSamplingProperty);
            set => SetValue(SmoothSamplingProperty, value);
        }

        public bool IsEraser
        {
            get => (bool)GetValue(IsEraserProperty);
            set => SetValue(IsEraserProperty, value);
        }

       

        private void DrawLine(Path path)
        {
            if (_PathVector2DDictionary[path].Count > 2)
            {
                var pathVector2Ds = _PathVector2DDictionary[path];
                var smoothNum = (int)(_PathVector2DDictionary[path].Count * SmoothSampling);
                if (smoothNum > 1)
                    pathVector2Ds = ComputingHelper.AverageSampling(_PathVector2DDictionary[path], smoothNum);
                var lineB = new LineB(pathVector2Ds, false);
                lineB.Tension = Tension;

                path.Data = lineB;
            }
        }

        private void btnClertCanvas_Click(object sender, RoutedEventArgs e)
        {
            drawingCanvas.Children.Clear();
            _PathVector2DDictionary.Clear();
        }
    }
}

到此这篇关于WPF+Canvas实现平滑笔迹的示例代码的文章就介绍到这了,更多相关WPF Canvas平滑笔迹内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Unity3D使用陀螺仪控制节点旋转

    Unity3D使用陀螺仪控制节点旋转

    这篇文章主要为大家详细介绍了Unity3D使用陀螺仪控制节点旋转,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-11-11
  • C# 运算符 ?、??、?: 各种问号的用法和说明

    C# 运算符 ?、??、?: 各种问号的用法和说明

    本文介绍C#中三种常见的问号运算符的使用方法,简单讲解给大家,希望对大家有所帮助。
    2016-04-04
  • C#读取word中表格数据的方法实现

    C#读取word中表格数据的方法实现

    本文主要介绍了C#读取word中表格数据的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • C#连接SQL Server数据库的实例讲解

    C#连接SQL Server数据库的实例讲解

    在本篇文章里小编给大家整理了关于C#连接SQL Server数据库的实例内容,有需要的朋友们参考学习下。
    2020-01-01
  • C#泛型概念的简介与泛型的使用

    C#泛型概念的简介与泛型的使用

    今天小编就为大家分享一篇关于C#泛型概念的简介与泛型的使用,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10
  • c# Linq常用的小技巧

    c# Linq常用的小技巧

    这篇文章主要介绍了c# Linq常用的小技巧,文中讲解非常详细,示例代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • C#使用linq查询大数据集的方法

    C#使用linq查询大数据集的方法

    这篇文章主要介绍了C#使用linq查询大数据集的方法,涉及C#调用linq进行数据查询的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04
  • C#实战之备忘录的制作详解

    C#实战之备忘录的制作详解

    这篇文章主要为大家介绍了如何利用C#制作一个备忘录,文中的示例代码讲解详细,对我们学习C#有一定的帮助,感兴趣的小伙伴可以学习一下
    2022-02-02
  • C#实现代码移除窗体上的控件

    C#实现代码移除窗体上的控件

    这篇文章主要介绍了C#实现代码移除窗体上的控件问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01
  • C#使用二维数组模拟斗地主

    C#使用二维数组模拟斗地主

    这篇文章主要介绍了C#使用二维数组模拟斗地主的方法,通过C#的二维数组简单实现扑克随机发牌的功能,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04

最新评论