开发者

WPF+Canvas实现平滑笔迹的示例代码

目录
  • 实现思路
  • 实现效果
  • 实现代码

实现思路

收集路径点集。

平均采样路径点集。

将路径点集转为 LineB

把 LineB 数据传给 Path

实现效果

WPF+Canvas实现平滑笔迹的示例代码

实现代码

1)Vector2D.cs 代码如下

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;

namespacewpFDevelopers.Samples.ExampleViews.CanvasHandwriting
{
publicclassVector2D
{
publicdoubleX{get;set;}=0;
publicdoubleY{get;set;}=0;

///<summary>
///向量的模
///</summary>
publicdoubleMold
{
get
{
//自身各分量平方运算.
doubleX=this.X*this.X;
doubleY=this.Y*this.Y;
returnMath.Sqrt(X+Y);//开根号,最终返回向量的长度/模/大小.
}
}
///<summary>
///单位向量
///</summary>
publicVector2DUnitVector
{
get
{
doublesumSquares=(X*X)+(Y*Y);
returnnewVector2D(X/Math.Sqrt(sumSquares),Y/Math.Sqrt(sumSquares));
}
}

publicVector2D()
{

}
publicVector2D(doublex,doubley)
{
X=x;
Y=y;
}
publicVector2D(System.Windows.Pointpoint)
{
X=point.X;
Y=point.Y;
}

publicvoidOffset(doubleangle,doubledistance,AngleTypeangleType=AngleType.Radian)
{
varvector2D=Vector2D.CalculateVectorOffset(this,angle,distance,angleType);
X=vector2D.X;
Y=vector2D.Y;
}
publicvoidRotate(doubleangle,Vector2DvectorCenter=null,AngleTypeangleType=AngleType.Radian)
{
vectorCenter=vectorCenter==null?this:vectorCenter;
varvector2D=Vector2D.CalculateVectorRotation(this,vectorCenter,angle,angleType);
X=vector2D.X;
Y=vector2D.Y;
}

#region静态方法
///<summary>
///计算两个向量之间的距离
///</summary>
publicstaticdoubleCalculateVectorDistance(Vector2Dvector2DA,Vector2Dvector2DB)
{
Vector2Dvector2D=vector2DA-vector2DB;
returnvector2D.Mold;
}

///<summary>
///计算两点夹角,右侧X轴线为0度,向下为正,向上为负
///</summary>
publicstaticdoubleIncludedAngleXAxis(Vector2Dvector2DA,Vector2Dvector2DB,AngleTypeangleType=AngleType.Radian)
{
doubleradian=Math.Atan2(vector2DB.Y-vector2DA.Y,vector2DB.X-vector2DA.X);//弧度:1.1071487177940904
returnangleType==AngleType.Radian?radian:ComputingHelper.RadianToAngle(radian);
}

///<summary>
///计算两点夹角,下侧Y轴线为0度,向右为正,向左为负
///</summary>
publicstaticdoubleIncludedAngleYAxis(Vector2Dvector2DA,Vector2Dvector2DB,AngleTypeangleType=AngleType.Radian)
{
doubleradian=Math.Atan2(vector2DB.X-vector2DA.X,vector2DB.Y-vector2DA.Y);//弧度:0.46364760900080609
returnangleType==AngleType.Radian?radian:ComputingHelper.RadianToAngle(radian);
}

///<summary>
///偏移向量到指定角度,指定距离
///</summary>
publicstaticVector2DCalculateVectorOffset(Vector2Dvector2D,doubleangle,doubledistance,AngleTypeangleType=AngleType.Radian)
{
Vector2DpointVector2D=newVector2D();
if(angleType==AngleType.Angle)
{
angle=angle/(180/Math.PI);//角度转弧度
}
doublewidth=Math.Cos(Math.Abs(angle))*distance;
doubleheight=Math.Sin(Math.Abs(angle))*distance;
if(angle<=Math.PI&&angle>=0)
//if(angleis<=Math.PIand>=0)
{
pointVector2D.X=vector2D.X-width;
pointVector2D.Y=vector2D.Y-height;
}
if(angle>=(-Math.PI)&&angle<=0)
//if(angleis>=(-Math.PI)and<=0)
{
pointVector2D.X=vector2D.X-width;
pointVector2D.Y=vector2D.Y+height;
}
returnpointVector2D;
}

///<summary>
///围绕一个中心点,旋转一个向量,相对旋转
///</summary>
publicstaticVector2DCalculateVectorRotation(Vector2Dvector2D,Vector2DvectorCenter,doubleradian,AngleTypeangleType=AngleType.Radian)
{
radian=angleType==AngleType.Radian?radian:ComputingHelper.RadianToAngle(radian);
doublex1=(vector2D.X-vectorCenter.X)*Math.Sin(radian)+(vector2D.Y-vectorCenter.Y)*Math.Cos(radian)+vectorCenter.X;
doubley1=-(vector2D.X-vectorCenter.X)*Math.Cos(radian)+(vector2D.Y-vectorCenter.Y)*Math.Sin(radian)+vectorCenter.Y;
returnnewVector2D(x1,y1);
}
publicstaticVector2DCalculateVectorCenter(Vector2Dvector2DA,Vector2Dvector2DB)
{
returnnewVector2D((vector2DA.X+vector2DB.X)/2,(vector2DA.Y+vector2DB.Y)/2);
}

///<summary>
///判断坐标点是否在多边形区域内,射线法
///</summary>
publicstaticboolIsPointPolygonalArea(Vector2Dvector2D,List<Vector2D>aolygonaArrayList)
{
varN=aolygonaArrayList.Count;
varboundOrVertex=true;//如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
varcrossNumber=0;//x的交叉点计数
varprecision=2e-10;//浮点类型计算时候与0比较时候的容差
Vector2Dp1,p2;//neighbourboundvertices
varp=vector2D;//测试点
p1=aolygonaArrayList[0];//leftvertex
for(vari=1;i<=N;++i)
{
//checkallrays
if(p.X.Equals(p1.X)&&p.Y.Equals(p1.Y))
{
returnboundOrVertex;//pisanvertex
}

p2=aolygonaArrayList[i%N];//rightvertex
if(p.X<Math.Min(p1.X,p2.X)||p.X>Math.Max(p1.X,p2.X))
{
//rayisoutsideofourinterests
p1=p2;
continue;//nextrayleftpoint
}

if(p.X>Math.Min(p1.X,p2.X)&&p.X<Math.Max(p1.X,p2.X))
{
//rayiscrossingoverbythealgorithm(commonpartof)
if(p.Y<=Math.Max(p1.Y,p2.Y))
{
//xisbeforeofray
if(p1.X==p2.X&&p.Y>=Math.Min(p1.Y,p2.Y))
{
//overliesonahorizontalray
returnboundOrVertex;
}

if(p1.Y==p2.Y)
{
//rayjsisvertical
if(p1.Y==p.Y)
{
//overliesonaverticalray
returnboundOrVertex;
}
else
{
//beforeray
++crossNumber;
}
}
else
{
//crosspointontheleftside
varxinters=
(p.X-p1.X)*(p2.Y-p1.Y)/(p2.X-p1.X)+
p1.Y;//crosspointofY
if(Math.Abs(p.Y-xinters)<precision)
{
//overliesonaray
returnboundOrVertex;
}

if(p.Y<xinters)
{
//beforeray
++crossNumber;
}
}
}
}
else
{
//specialcasewhenrayiscrossingthroughthevertex
if(p.X==p2.X&&p.Y<=p2.Y)
{
//pcrossingoverp2
varp3=aolygonaArrayList[(i+1)%N];//nextvertex
if(p.X>=Math.Min(p1.X,p3.X)&&p.X<=Math.Max(p1.X,p3.X))
{
//p.Xliesbetweenp1.X&p3.X
++crossNumber;
}
else
{
crossNumber+=2;
}
}
}
p1=p2;//nextrayleftpoint
}
if(crossNumber%2==0)
{
//偶数在多边形外
returnfalse;
}
else
{
//奇数在多边形内
returntrue;
}
}

///<summary>
///判断一个点是否在一条边内
///</summary>

publicstaticboolIsPointEdge(Vector2Dpoint,Vector2DstartPoint,Vector2DendPoint)
{
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>
publicstaticVector2Doperator+(Vector2Dvector2DA,Vector2Dvector2DB)
{
Vector2Dvector2D=newVector2D();
vector2D.X=vector2DA.X+vector2DB.X;
vector2D.Y=vector2DA.Y+vector2DB.Y;
returnvector2D;
}

///<summary>
///重载运算符,差运算,可以用来计算两向量距离
///</summary>
publicstaticVector2Doperator-(Vector2Dvector2DA,Vector2Dvector2DB)
{
Vector2Dvector2D=newVector2D();
vector2D.X=vector2DA.X-vector2DB.X;
vector2D.Y=vector2DA.Y-vector2DB.Y;
returnvector2D;
}

///<summary>
///重载运算符,差运算,可以用来计算两向量距离
///</summary>
publicstaticVector2Doperator-(Vector2Dvector2D,double_float)
{
returnnewVector2D(vector2D.X-_float,vector2D.Y-_float);
}

///<summary>
///重载运算符,点积运算,可以用来计算两向量夹角
///</summary>
publicstaticdoubleoperator*(Vector2Dvector2DA,Vector2Dvector2DB)
{
return(vector2DA.X*vector2DB.X)+(vector2DA.Y*vector2DB.Y);
}
publicstaticdoubleoperator*(Vector2Dvector2D,double_float)
{
return(vector2D.X*_float)+(vector2D.Y*_float);
}

///<summary>
///重载运算符,点积运算,可以用来计算两向量夹角
///</summary>
publicstaticdoubleoperator/(Vector2Dvector2D,doublepara)
{
return(vector2D.X/para)+(vector2D.Y/para);
}

///<summary>
///重载运算符
///</summary>

publicstaticbooloperator>=(Vector2Dvector2D,doublepara)
{
if(vector2D.Mold>=para)
{
returntrue;
}
else
{
returnfalse;
}
}

publicstaticbooloperator<=(Vector2Dvector2D,doublepara)
{
if(vector2D.Mold<=para)
{
returntrue;
}
else
{
returnfalse;
}
}

publicstaticbooloperator>(Vector2Dvector2D,doublepara)
{
if(vector2D.Mold>para)
{
returntrue;
}
else
{
returnfalse;
}
}

publicstaticbooloperator<(Vector2Dvector2D,doublepara)
{
if(vector2D.Mold<para)
{
returntrue;
}
else
{
returnfalse;
}
}
#endregion运算符重载

#region隐式转换
///<summary>
///重载隐式转换,可以直接使用Point
///</summary>
///<paramname="v"></param>
publicstaticimplicitoperatorVector2D(System.Windows.Pointv)//隐式转换
{
returnnewVector2D(v.X,v.Y);
}
///<summary>
///重载隐式转换,可以直接使用Point
///</summary>
///<paramname="v"></param>
publicstaticimplicitoperatorSystem.Windows.Point(Vector2Dv)//隐式转换
{
returnnewSystem.Windows.Point(v.X,v.Y);
}
///<summary>
///重载隐式转换,可以直接使用double
///</summary>
///<paramname="v"></param>
publicstaticimplicitoperatorVector2D(doublev)//隐式转换
{
returnnewVector2D(v,v);
}
#endregion隐式转换

#regionToString
publicoverridestringToString()
{
returnX.ToString()+","+Y.ToString();
}
publicstringToString(stringsymbol)
{
returnX.ToString()+symbol+Y.ToString();
}
publicstringToString(stringsender,stringsymbol)
{
returnX.ToString(sender)+symbol+Y.ToString(sender);
}
#endregion
}
publicenumAngleType
{
Angle,
Radian
}
}

2)ComputingHelper.cs 代码如下

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;

namespaceWPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{
publicstaticclassComputingHelper
{
publicstaticdoubleAngleToRadian(doubleangle)
{
returnangle*(Math.PI/180);
}
publicstaticdoubleRadianToAngle(doubleradian)
{
returnradian*(180/Math.PI);
}

///<summary>
///将一个值从一个范围映射到另一个范围
///</summary>
publicstaticdoubleRangeMapping(doubleinputValue,doubleenterLowerLimit,doubleenterUpperlimit,doubleoutputLowerLimit,doubleOutputUpperLimit,CurveTypecurveType=CurveType.None)
{
varpercentage=(enterUpperLimit-inputValue)/(enterUpperLimit-enterLowerLimit);
switch(curveType)
{
caseCurveType.Sine:
percentage=Math.Sin(percentage);
break;
caseCurveType.CoSine:
percentage=Math.Cos(percentage);
break;
caseCurveType.Tangent:
percentage=Math.Tan(percentage);
break;
caseCurveType.Cotangent:
percentage=Math.Atan(percentage);
break;
default:
break;
}
doubleoutputValue=OutputUpperLimit-((OutputUpperLimit-outputLowerLimit)*percentage);

returnoutputValue;
}

publicstaticstringByteToKB(double_byte)
{
List<string>unit=newList<string>(){"B","KB","MB","GB","TB","P","PB"};
inti=0;
while(_b编程客栈yte>1024)
{
_byte/=1024;
i++;
}
_byte=Math.Round(_byte,3);//保留三位小数
return_byte+unit[i];
}

///<summary>
///缩短一个数组,对其进行平均采样
///</summary>

publicstaticdouble[]AverageSampling(double[]sourceArray,intnumber)
{
if(sourceArray.Length<=number)
{
returnsourceArray;
//thrownewException("新的数组必须比原有的要小!");
}
double[]arrayList=newdouble[number];
doublestride=(double)sourceArray.Length/number;
for(inti=0,jIndex=0;i<number;i++,jIndex++)
{
doublestrideIncrement=i*stride;
strideIncrement=Math.Round(strideIncrement,6);
doublesum=0;
intfirstIndex=(int)(strideIncrement);
doublefirstDecimal=strideIncrement-firstIndex;

inttailIndex=(int)(strideIncrement+stride);
doubletailDecimal=(strideIncrement+stride)-tailIndex;

if(firstDecimal!=0)
sum+=sourceArray[firstIndex]*(1-firstDecimal);

if(tailDecimal!=0&&tailIndex!=sourceArray.Length)
sum+=sourceArray[tailIndex]*(tailDecimal);

intstartIndex=firstDecimal==0?firstIndex:firstIndex+1;
intendIndex=tailIndex;

for(intj=startIndex;j<endIndex;j++)
sum+=sourceArray[j];

arrayList[jIndex]=sum/stride;
}
returnarrayList;
}
publicstaticList<Vector2D>AverageSampling(List<Vector2D>sourceArray,intnumber)
{
if(sourceArray.Count<=number-2)
{
returnsourceArray;
}
double[]x=newdouble[sourceArray.Count];
double[]y=newdouble[sourceArray.Count];
for(inti=0;i<sourceArray.Count;i++)
{
x[i]=sourceArray[i].X;
y[i]=sourceArray[i].Y;
}

double[]X=AverageS编程客栈ampling(x,number-2);
double[]Y=AverageSampling(y,number-2);

List<Vector2D>arrayList=newList<Vector2D>();
for(inti=0;i<number-2;i++)
{
arrayList.Add(newVector2D(X[i],Y[i]));
}

arrayList.Insert(0,sourceArray[0]);//添加首
arrayList.Add(sourceArray[sourceArray.Count-1]);//添加尾

returnarrayList;
}
}
publicenumCurveType
{
Sine,
CoSine,
Tangent,
Cotangent,
None
}
}

3)LineB.cs 代码如下

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Windows.Media;

namespaceWPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{
publicclassLineB
{
privateList<Vector2D>_vector2DList=newList<Vector2D>();
publicList<Vector2D>Vector2DList
{
get{return_vector2DList;}
set
{
_vector2DList=value;
}
}

privateList<BezierCurve>_bezierCurveList=newList<BezierCurve>();
publicList<BezierCurve>BezierCurveList
{
get{return_bezierCurveList;}
privateset{_bezierCurveList=value;}
}


privatedouble_tension=0.618;
publicdoubleTension
{
get{return_tension;}
set
{
_tension=value;
if(_tension>10)
_tension=10;
if(_tension<0)
_tension=0;
}
}

privatebool_isClosedCurve=true;
publicboolIsClosedCurve
{
get{return_isClosedCurve;}
set{_isClosedCurve=value;}
}

privatestring_pathData=string.Empty;
publicstringPathData
{
get
{
if(_pathData==string.Empty)
{
_pathData=Vector2DToBezierCurve();
}
return_pathData;
}
}

privatestringVector2DToBezierCurve()
{
if(Vector2DList.Count<3)
returnstring.Empty;

BezierCurveList.Clear();
for(inti=0;i<Vector2DList.Count;i++)
{
intpointTwoIndex=i+1<Vector2DList.Count?i+1:0;
intpointThreeIndex=i+2<Vector2DList.Count?i+2:i+2-Vector2DList.Count;
Vector2Dvector2D1=Vector2DList[i];
Vector2Dvector2D2=Vector2DList[pointTwoIndex];
Vector2Dvector2D3=Vector2DList[pointThreeIndex];

Vector2DstartVector2D=Vector2D.CalculateVectorCenter(vector2D1,vector2D2);
doublestartAngle=Vector2D.IncludedAngleXAxis(vector2D1,vector2D2);
doublestartDistance=Vector2D.CalculateVectorDistance(startVector2D,vector2D2)*(1-Tension);
Vector2DstartControlPoint=Vector2D.CalculateVectorOffset(vector2D2,startAngle,startDistance);

Vector2DendVector2D=Vector2D.CalculateVectorCenter(vector2D2,vector2D3);
doubleendAngle=Vector2D.IncludedAngleXAxis(endVector2D,vector2D2);
doubleendDistance=Vector2D.CalculateVectorDistance(endVector2D,vector2D2)*(1-Tension);
Vector2DendControlPoint=Vector2D.CalculateVectorOffset(endVector2D,endAngle,endDistance);

BezierCurvebezierCurve=newBezierCurve();
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;
}
stringpath=$"M{BezierCurveList[0].StartVector2D.ToString()}";
foreach(variteminBezierCurveList)
{
path+=$"C{item.StartControlPoint.ToString("")},{item.EndControlPoint.ToString("")},{item.EndVector2D.ToString("")}";
}
returnpath;
}

publicLineB()
{
}
publicLineB(List<Vector2D>verVector2DList,boolisClosedCurve=true)
{
this.Vector2DList=verVector2DList;
this.IsClosedCurve=isClosedCurve;
}

///<summary>
///重载隐式转换,可以直接使用Point
///</summary>
///<paramname="v"></param>
publicstaticimplicitoperatorGeometry(LineBlineB)//隐式转换
{
returnGeometry.Parse(lineB.PathData);
}
}
publicclassBezierCurve
{
privateVector2D_startVector2D=newVector2D(0,0);
publicVector2DStartVector2D
{
get{return_startVector2D;}
set{_startVector2D=value;}
}

privateVector2D_startControlPoint=newVector2D(0,100);
publicVector2DStartControlPoint
{
get{return_startControlPoint;}
set{_startControlPoint=value;}
}

privateVector2D_endControlPoint=newVector2D(100,0);
publicVector2DEndControlPoint
{
get{return_endControlPoint;}
set{_endControlPoint=value;}
}

privateVector2D_endVector2D=newVector2D(100,100);
publicVector2DEndVector2D
{
get{return_endVector2D;}
set{_endVector2D=value;}
}


}
}

4)CanvasHandWritingExample.xaml 代码如下

<UserControlx: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>
<StyleTargetType="{x:TypeTextblock}">
<SetterProperty="Foreground"Value="{StaticResourcePrimaryTextSolidColorBrush}"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinitionHeight="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanelOrientation="Horizontal"Margin="4">
<TextBlockText="张力:"VerticalAlignment="Center"/>
<TextBoxText="{BindingTension,RelativeSource={RelativeSourceAncestorType=local:CanvasHandWritingExample}}"/>
<SandroidliderWidth="100"SmallChange="0.01"
Value="{BindingTension,RelativeSource={RelativeSourceAncestorType=local:CanvasHandWritingExample}}"Maximum="1"
VerticalAlignment="Center"
Margin="5,0"/>
<TextBlockText="平滑采样:"VerticalAlignment="Center"/>
<TextBoxText="{BindingSmoothSampling,RelativeSource={RelativeSourceAncestorType=local:CanvasHandWritingExample}}"
Margin="5,0"/>
<SliderValue="{BindingSmoothSampling,RelativeSource={RelativeSourceAncestorType=local:CanvasHandWritingExample}}"
Width="100"
VerticalAlignment="Center"
SmallChange="0.01"Maximum="1"
TickFrequency="0.1"/>
<CheckBoxContent="橡皮擦"
VerticalAlignment="Center"
Margin="5,0"
IsChecked="{BindingIsEraser,RelativeSource={RelativeSourceAncestorType=local:CanvasHandWritingExample}}"/>
<ButtonContent="清空画布"Click="btnClertCanvas_Click"/>
</StackPanel>
<Canvasx:Name="drawingCanvas"
Grid.Row="1"Background="Black"
PreviewMouseLeftButtonDown="DrawingCanvas_PreviewMouseLeftButtonDown"
PreviewMouseMove="DrawingCanvas_PreviewMouseMove"
PreviewMouseLeftButtonUp="DrawingCanvas_PreviewMouseLeftButtonUp"/>

</Grid>
</UserControl&gpythont;

5)CanvasHandWritingExample.xaml.cs 代码如下

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Threading;
usingSystem.Threading.Tasks;
usingSystem.Windows;
usingSystem.Windows.Controls;
usingSystem.Windows.Input;
usingSystem.Windows.Media;
usingSystem.Windows.Shapes;

namespaceWPFDevelopers.Samples.ExampleViews.CanvasHandWriting
{
///<summary>
///CanvasHandWritingExample.xaml的交互逻辑
///</summary>
publicpartialclassCanvasHandWritingExample:UserControl
{
publicstaticreadonlyDependencyPropertyTensionProperty=
DependencyProperty.Register("Tension",typeof(double),typeof(CanvasHandWritingExample),
newPropertyMetadata(0.618));

publicstaticreadonlyDependencyPropertySmoothSamplingProperty=
DependencyProperty.Register("SmoothSampling",typeof(double),typeof(CanvasHandWritingExample),
newUIPropertyMetadata(OnSmoothSamplingChanged));

publicstaticreadonlyDependencyPropertyIsEraserProperty=
DependencyProperty.Register("IsEraser",typeof(bool),typeof(CanvasHandWritingExample),
newPropertyMetadata(false));

privatereadonlyDictionary<Path,List<Vector2D>>_PathVector2DDictionary;

volatilebool_IsStart=false;
Path_DrawingPath=default;

privatestaticvoidOnSmoothSamplingChanged(DependencyObjectd,DependencyPropertyChangedEventArgse)
{
varmWindow=(CanvasHandWritingExample)d;
foreach(variteminmWindow._PathVector2DDictionary.Keys)
{
mWindow.DrawLine(item);
}
}
publicCanvasHandWritingExample()
{
InitializeComponent();
_PathVector2DDictionary=newDictionary<Path,List<Vector2D>>();
SmoothSampling=0.8;
}

privatevoidDrawingCanvas_PreviewMouseLeftButtonDown(objectsender,MouseButtonEventArgse)
{
_IsStart=true;
_DrawingPath=newPath()
{
StrokeDashCap=PenLineCap.Round,
StrokeStartLineCap=PenLineCap.Round,
StrokeEndLineCap=PenLineCap.Round,
StrokeLineJoin=PenLineJoin.Round,
};

if(IsEraser)
{
_DrawingPath.Stroke=newSolidColorBrush(Colors.Black);
_DrawingPath.StrokeThickness=40;
}
else
{
varrandom=newRandom();
varstrokeBrush=newSolidColorBrush(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,newList<Vector2D>());
drawingCanvas.Children.Add(_DrawingPath);
}

privatevoidDrawingCanvas_PreviewMouseLeftButtonUp(objectsender,MouseButtonEventArgse)
{
_IsStart=false;
_DrawingPath=default;
}

privatevoidDrawingCanvas_PreviewMouseMove(objectsender,MouseEventArgse)
{
if(!_IsStart)
return;

if(_DrawingPathisnull)
return;

Vector2DcurrenPoint=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[_Dra开发者_C学习wingPath][_PathVector2DDictionary[_DrawingPath].Count-1])>1)
_PathVector2DDictionary[_DrawingPath].Add(e.GetPosition(drawingCanvas));
}
else
_PathVector2DDictionary[_DrawingPath].Add(e.GetPosition(drawingCanvas));

DrawLine(_DrawingPath);
}


publicdoubleTension
{
get=>(double)GetValue(TensionProperty);
set=>SetValue(TensionProperty,value);
}

publicdoubleSmoothSampling
{
get=>(double)GetValue(SmoothSamplingProperty);
set=>SetValue(SmoothSamplingProperty,value);
}

publicboolIsEraser
{
get=>(bool)GetValue(IsEraserProperty);
set=>SetValue(IsEraserProperty,value);
}



privatevoidDrawLine(Pathpath)
{
if(_PathVector2DDictionary[path].Count>2)
{
varpathVector2Ds=_PathVector2DDictionary[path];
varsmoothNum=(int)(_PathVector2DDictionary[path].Count*SmoothSampling);
if(smoothNum>1)
pathVector2Ds=ComputingHelper.AverageSampling(_PathVector2DDictionary[path],smoothNum);
varlineB=newLineB(pathVector2Ds,false);
lineB.Tension=Tension;

path.Data=lineB;
}
}

privatevoidbtnClertCanvas_Click(objectsender,RoutedEventArgse)
{
drawingCanvas.Children.Clear();
_PathVector2DDictionary.Clear();
}
}
}

到此这篇关于WPF+Canvas实现平滑笔迹的示例代码的文章就介绍到这了,更多相关WPF Canvas平滑笔迹内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜