WPF Canvas 平滑笔迹

 WPF Canvas 平滑笔迹

控件名:CanvasHandWriting

作者:小封(邝攀升)

原文链接:   https://github.com/WPFDevelopersOrg/WPFDevelopers

编辑:驚鏵

完整的思路如下

  • 收集路径点集。

  • 平均采样路径点集。

  • 将路径点集转为 LineB

  • LineB 数据传给 Path

4ec686048b47b36e3752d68e89a66376.gif

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();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NetSpeed

NetSpeed公司提供的NOC包括三部分&#xff0c;可以通过NocStudio进行配置生成。 1)NetSpeed Orion&#xff0c;面向快速SoC design的可综合平台。 2)Linley NetSpeed NoC面向复杂的interconnect实现&#xff0c;同时优化内部physical implementation和timing closure. NoC是基于…

js ajax java传参_ajax参数传递与后台接收

ajax参数传递与后台接收Servlet中读取http参数的方法Enumeration getParameterNames() 返回一个 String 对象的枚举&#xff0c;包含在该请求中包含的参数的名称String getParameter(String name) 以字符串形式返回请求参数的值&#xff0c;或者如果参数不存在则返回 null。Str…

init 访问器只能初始化时赋值,是真的吗?

前言C# 提供的 init 关键字用于在属性中定义访问器方法&#xff0c;可以让属性仅能在对象初始化的时候被赋值&#xff0c;其他时候只能为只读属性的形式。例如下面代码可以正常执行&#xff1a;public class Demo {public string Name { get; init; } }var demo new Demo { Na…

eclipse实现代码块折叠-类似于VS中的#region……#endregion

背 景 刚才在写代码的时候&#xff0c;写了十几行可以说是重复的代码&#xff1a; 如果整个方法或类中代码多了&#xff0c;感觉它们太TM占地方了&#xff0c;给读者在阅读代码上造成很大的困难&#xff0c;于是想到能不能把他们“浓缩”成一行&#xff0c;脑子里第一个闪现出的…

java定义基础变量语句_java语言基础-变量

一丶变量的基本概念1.什么是变量(1).内存中的一个存储区域(2).该区域有自己的名称(变量名),和类型(数据类型)(3.)该区域的数据可以在同一类型范围内不断变化(定义变量的主要目的是因为数据的不确定性)2.为什么要定义变量用来不断存放同一类型的常量&#xff0c;并可以重复使用3…

C# WPF MVVM模式[经典]案例

01—前言Caliburn.Micro(简称CM)一经推出便备受推崇&#xff0c;作为一款MVVM开发模式的经典框架&#xff0c;越来越多的受到wpf开发者的青睐.我们看一下官方的描述&#xff1a;Caliburn是一个为Xaml平台设计的小型但功能强大的框架。Micro实现了各种UI模式&#xff0c;用于解决…

shell数组

定义数组[rootwy shell]# a(1 2 3 4)显示数组[rootwy shell]# echo ${a[]}1 2 3 4[rootwy shell]# echo ${a[*]}1 2 3 4显示数组中的某个元素[rootwy shell]# echo ${a[0]}1增加元素[rootwy shell]# a[4]9[rootwy shell]# echo ${a[*]}1 2 3 4 9修改元素值 [rootwy shell]# a[2…

LINUX中常用操作命令

LINUX中常用操作命令 引用&#xff1a;http://www.daniubiji.cn/archives/25 Linux简介及Ubuntu安装 常见指令系统管理命令打包压缩相关命令关机/重启机器Linux管道Linux软件包管理vim使用用户及用户组管理文件权限管理Linux简介及Ubuntu安装 Linux&#xff0c;免费开源&#x…

Log4j编写

来自: http://www.blogjava.net/zJun/archive/2006/06/28/55511.html Log4J的配置文件(Configuration File)就是用来设置记录器的级别、存放器和布局的&#xff0c;它可接keyvalue格式的设置或xml格式的设置信息。通过配置&#xff0c;可以创建出Log4J的运行环境。1. 配置文件L…

C# 为什么高手喜欢用StartsWith而不是Substring进行字符串匹配?

字符串的截取匹配操作在开发中非常常见&#xff0c;比如下面这个示例&#xff1a;我要匹配查找出来字符串数组中以“abc”开头的字符串并打印&#xff0c;我下面分别用了两种方式实现&#xff0c;代码如下&#xff1a;using System;namespace ConsoleApp23 {class Program{stat…

Nginx 服务器开启status页面检测服务状态

原文&#xff1a;http://www.cnblogs.com/hanyifeng/p/5830013.html 一、Nginx status monitor 和apache 中服务器状态一样。输出的内容如&#xff1a; 第1列&#xff1a; 当前与http建立的连接数&#xff0c;包括等待的客户端连接&#xff1a;2第2列&#xff1a;接受的客户端连…

在OpenCloudOS 上安装.NET 6

开源操作系统社区 OpenCloudOS 由腾讯与合作伙伴共同倡议发起&#xff0c;是完全中立、全面开放、安全稳定、高性能的操作系统及生态。OpenCloudOS 沉淀了多家厂商在软件和开源生态的优势&#xff0c;继承了腾讯在操作系统和内核层面超过10年的技术积累&#xff0c;在云原生、稳…

java产生的数字发送到页面_JAVA中数字证书的维护及生成方法

Java中的keytool.exe可以用来创建数字证书&#xff0c;所有的数字证书是以一条一条(采用别名区别)的形式存入证书库的中&#xff0c;证书库中的一条证书包含该条证书的私钥&#xff0c;公钥和对应的数字证书的信息。证书库中的一条证书可以导出数字证书文件&#xff0c;数字证书…

IDEA破解 2017 IDEA license server 激活(可用)

进入ide主页面&#xff0c;help-register-license server,然后输入 http://idea.iteblog.com/key.PHP&#xff08;注意&#xff1a;php要小写&#xff09;即可~ 转载于:https://www.cnblogs.com/austinspark-jessylu/p/7232982.html

《ASP.NET Core 6框架揭秘》实例演示[20]:“数据保护”框架基于文件的密钥存储...

《数据加解密与哈希》演示了“数据保护”框架如何用来对数据进行加解密&#xff0c;而“数据保护”框架的核心是“密钥管理”。数据保护框架以XML的形式来存储密钥&#xff0c;默认的IKeyManager实现类型为XmlKeyManager。接下来我们通过模拟代码和实例演示的形式来介绍一下Xml…

使用msui的回到顶部的一个小问题

2019独角兽企业重金招聘Python工程师标准>>> 回到顶部&#xff0c;一直没反应。 zepto加了动画后&#xff0c;依然如此。原生写法&#xff0c;jquery写法&#xff0c;仍然没有反应。 排查了后&#xff0c;发现获取的对象错误。手机端上&#xff0c;滚动到顶部&#…

口袋精灵加速版java_口袋妖怪TCG!口袋对决加速版教程(免ROOT)

原标题&#xff1a;口袋妖怪TCG&#xff01;口袋对决加速版教程(免ROOT)《口袋对决》是一款以口袋精灵对战为核心玩法的集换式卡牌游戏&#xff0c;原汁原味的口袋画风&#xff0c;丰富多彩的各系精灵&#xff0c;策略竞技的属性相克。在5分钟一局的卡牌对战中&#xff0c;你将…

C#发布程序添加其他程序文件

注&#xff1a;程序发布文件&#xff0c;默认只发布自身程序直接引用的相关文件(A程序)。 如果需要添加其他程序(不同的应用程序B)文件&#xff0c;操作方法如下&#xff1a; 第一步&#xff1a;将B程序文件复制到A程序 第二步&#xff1a;将B程序文件右键--》属性做如下更改&a…

k8s 读书笔记 - 初始化容器 Init Container

Init Container 概述Init Container&#xff08;Init 容器&#xff09; 是一种特殊容器&#xff0c;在 Pod 内的应用容器启动之前运行&#xff0c;执行相关的初始化操作。Init 容器可以包括一些应用镜像中不存在的 实用工具 和 安装脚本 。每个 Pod 中可以包含一个或多个容器&a…

捕获 BackgroundService 中的异常 | 学学官方是如何实现的

前言上次&#xff0c;我们实现了《使用“装饰者模式”捕获 BackgroundService 中的异常》。结果发现&#xff0c;微软已经发现了这个问题&#xff0c;并在 .NET 6 中解决了。&#xff08;囧&#xff09;让我们验证一下&#xff1a;using IHost host Host.CreateDefaultBuilder…