?WPF Canvas 平滑筆跡
控件名:CanvasHandWriting
作者:小封(鄺攀升)
原文鏈接:? ?https://github.com/WPFDevelopersOrg/WPFDevelopers
編輯:驚鏵
完整的思路如下
收集路徑點集。
平均采樣路徑點集。
將路徑點集轉為 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.1071487177940904return?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.46364760900080609return?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;?//如果點位于多邊形的頂點或邊上,也算做點在多邊形內,直接返回truevar?crossNumber?=?0;?//x的交叉點計數var?precision?=?2e-10;?//浮點類型計算時候與0比較時候的容差Vector2D?p1,?p2;?//neighbour?bound?verticesvar?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?rayreturn?boundOrVertex;}if?(p1.Y?==?p2.Y){//ray?is?vertical????????????????????????if?(p1.Y?==?p.Y){//overlies?on?a?vertical?rayreturn?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?rayreturn?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?ToStringpublic?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();}}
}