WPF Canvas平滑笔迹
小封(邝攀升) 人气:0实现思路
收集路径点集。
平均采样路径点集。
将路径点集转为 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(); } } }
加载全部内容