WPF+Canvas實(shí)現(xiàn)平滑筆跡的示例代碼
實(shí)現(xiàn)思路
收集路徑點(diǎn)集。
平均采樣路徑點(diǎn)集。
將路徑點(diǎn)集轉(zhuǎn)為 LineB。
把 LineB 數(shù)據(jù)傳給 Path。
實(shí)現(xiàn)效果
實(shí)現(xiàn)代碼
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 ????????????{ ????????????????//自身各分量平方運(yùn)算. ????????????????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?靜態(tài)方法 ????????///?<summary> ????????///?計(jì)算兩個向量之間的距離 ????????///?</summary> ????????public?static?double?CalculateVectorDistance(Vector2D?vector2DA,?Vector2D?vector2DB) ????????{ ????????????Vector2D?vector2D?=?vector2DA?-?vector2DB; ????????????return?vector2D.Mold; ????????} ????????///?<summary> ????????///?計(jì)算兩點(diǎn)夾角,右側(cè)X軸線為0度,向下為正,向上為負(fù) ????????///?</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> ????????///?計(jì)算兩點(diǎn)夾角,下側(cè)Y軸線為0度,向右為正,向左為負(fù) ????????///?</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);//角度轉(zhuǎn)弧度 ????????????} ????????????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> ????????///?圍繞一個中心點(diǎn),旋轉(zhuǎn)一個向量,相對旋轉(zhuǎn) ????????///?</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> ????????///?判斷坐標(biāo)點(diǎn)是否在多邊形區(qū)域內(nèi),射線法 ????????///?</summary> ????????public?static?bool?IsPointPolygonalArea(Vector2D?vector2D,?List<Vector2D>?aolygonaArrayList) ????????{ ????????????var?N?=?aolygonaArrayList.Count; ????????????var?boundOrVertex?=?true;?//如果點(diǎn)位于多邊形的頂點(diǎn)或邊上,也算做點(diǎn)在多邊形內(nèi),直接返回true ????????????var?crossNumber?=?0;?//x的交叉點(diǎn)計(jì)數(shù) ????????????var?precision?=?2e-10;?//浮點(diǎn)類型計(jì)算時候與0比較時候的容差 ????????????Vector2D?p1,?p2;?//neighbour?bound?vertices ????????????var?p?=?vector2D;?//測試點(diǎn) ????????????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) ????????????{ ????????????????//偶數(shù)在多邊形外 ????????????????return?false; ????????????} ????????????else ????????????{ ????????????????//奇數(shù)在多邊形內(nèi) ????????????????return?true; ????????????} ????????} ????????///?<summary> ????????///?判斷一個點(diǎn)是否在一條邊內(nèi) ????????///?</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?靜態(tài)方法 ????????#region?運(yùn)算符重載 ????????///?<summary> ????????///?重載運(yùn)算符,和運(yùn)算,可以用來計(jì)算兩向量距離 ????????///?</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> ????????///?重載運(yùn)算符,差運(yùn)算,可以用來計(jì)算兩向量距離 ????????///?</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> ????????///?重載運(yùn)算符,差運(yùn)算,可以用來計(jì)算兩向量距離 ????????///?</summary> ????????public?static?Vector2D?operator?-(Vector2D?vector2D,?double?_float) ????????{ ????????????return?new?Vector2D(vector2D.X?-?_float,?vector2D.Y?-?_float); ????????} ????????///?<summary> ????????///?重載運(yùn)算符,點(diǎn)積運(yùn)算,可以用來計(jì)算兩向量夾角 ????????///?</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> ????????///?重載運(yùn)算符,點(diǎn)積運(yùn)算,可以用來計(jì)算兩向量夾角 ????????///?</summary> ????????public?static?double?operator?/(Vector2D?vector2D,?double?para) ????????{ ????????????return?(vector2D.X?/?para)?+?(vector2D.Y?/?para); ????????} ????????///?<summary> ????????///?重載運(yùn)算符 ????????///?</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?運(yùn)算符重載 ????????#region?隱式轉(zhuǎn)換 ????????///?<summary> ????????///?重載隱式轉(zhuǎn)換,可以直接使用Point ????????///?</summary> ????????///?<param?name="v"></param> ????????public?static?implicit?operator?Vector2D(System.Windows.Point?v)//隱式轉(zhuǎn)換 ????????{ ????????????return?new?Vector2D(v.X,?v.Y); ????????} ????????///?<summary> ????????///?重載隱式轉(zhuǎn)換,可以直接使用Point ????????///?</summary> ????????///?<param?name="v"></param> ????????public?static?implicit?operator?System.Windows.Point(Vector2D?v)//隱式轉(zhuǎn)換 ????????{ ????????????return?new?System.Windows.Point(v.X,?v.Y); ????????} ????????///?<summary> ????????///?重載隱式轉(zhuǎn)換,可以直接使用double ????????///?</summary> ????????///?<param?name="v"></param> ????????public?static?implicit?operator?Vector2D(double?v)//隱式轉(zhuǎn)換 ????????{ ????????????return?new?Vector2D(v,?v); ????????} ????????#endregion?隱式轉(zhuǎn)換 ????????#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);//保留三位小數(shù) ????????????return?_byte?+?unit[i]; ????????} ????????///?<summary> ????????///?縮短一個數(shù)組,對其進(jìn)行平均采樣 ????????///?</summary> ???????? ????????public?static?double[]?AverageSampling(double[]?sourceArray,?int?number) ????????{ ????????????if?(sourceArray.Length?<=?number) ????????????{ ????????????????return?sourceArray; ????????????????//throw?new?Exception("新的數(shù)組必須比原有的要小!"); ????????????} ????????????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> ????????///?重載隱式轉(zhuǎn)換,可以直接使用Point ????????///?</summary> ????????///?<param?name="v"></param> ????????public?static?implicit?operator?Geometry(LineB?lineB)//隱式轉(zhuǎn)換 ????????{ ????????????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(); ????????} ????} }
到此這篇關(guān)于WPF+Canvas實(shí)現(xiàn)平滑筆跡的示例代碼的文章就介紹到這了,更多相關(guān)WPF Canvas平滑筆跡內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#學(xué)習(xí)筆記- 隨機(jī)函數(shù)Random()的用法詳解
下面小編就為大家?guī)硪黄狢#學(xué)習(xí)筆記- 隨機(jī)函數(shù)Random()的用法詳解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08C#實(shí)現(xiàn)將窗體固定在顯示器的左上角且不能移動的方法
這篇文章主要介紹了C#實(shí)現(xiàn)將窗體固定在顯示器的左上角且不能移動的方法,涉及C#窗體固定操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08C# WinForm調(diào)用Shell_NotifyIcon的示例代碼
這篇文章主要介紹了C# WinForm調(diào)用Shell_NotifyIcon的示例代碼,幫助大家更好的理解和使用c#,感興趣的朋友可以了解下2020-11-11