目录
- 实现思路
- 实现效果
- 实现代码
实现思路
收集路径点集。
平均采样路径点集。
将路径点集转为 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();
}
}
}