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平滑笔迹内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
相关文章